From 3a3ccfe51bdc9a0d1bcc0b5e08a1f4d3a35a79a2 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Wed, 31 Aug 2022 14:09:17 -0700 Subject: [PATCH] fix(apis): remove deprecated flightrecorder/recording apis (#438) * fix(docs): remove docs on deprecated apis * fix(apis): remove deprecated api source code * fix(controllers): remove deprecated controllers * fix(client): remove cryostat client resources * fix(tests): remove test resources for deprecated apis * fix(manager): unregister deprecated controllers * fix(manifests): remove crd patches for deprecated apis * fix(manifests): remove sample manifests for deprecated apis * fix(manifests): remove roles for deprecated api * fix(project-config): remove deprecated apis from PROJECT file * fix(manifests): remove deprecated api crds * !fixup(manifests): completely remove rbac for deprecated apis * fix(csv): edit csv after removing deprecated apis & bump operator-sdk builder annotation to 1.22.2 * fix(resources): remove endpoints from list of watched resources * fix(role): remove access to endpoints for cryostat role * fix(tests): continue to remove all test resources for deprecated apis * fix(bundles): update bundle * fix(role): add back rule to access endpoints for CR * fix(rbac): add back endpoints to role for controller * chore(crds): clean up description markers * fix(makefile): update undeploy target to remove recording api * chore(makefile): fix typo in comments * fix(common): fix logger name --- Makefile | 3 +- PROJECT | 18 - README.md | 4 +- api/v1beta1/cryostat_types.go | 74 +-- api/v1beta1/flightrecorder_types.go | 181 ----- api/v1beta1/recording_types.go | 146 ---- api/v1beta1/zz_generated.deepcopy.go | 320 --------- ...yostat-operator.clusterserviceversion.yaml | 270 +------- .../operator.cryostat.io_cryostats.yaml | 50 +- .../operator.cryostat.io_flightrecorders.yaml | 252 ------- .../operator.cryostat.io_recordings.yaml | 122 ---- .../bases/operator.cryostat.io_cryostats.yaml | 50 +- .../operator.cryostat.io_flightrecorders.yaml | 247 ------- .../operator.cryostat.io_recordings.yaml | 117 ---- config/crd/kustomization.yaml | 6 - .../cainjection_in_flightrecorders.yaml | 7 - .../patches/cainjection_in_recordings.yaml | 7 - .../patches/webhook_in_flightrecorders.yaml | 14 - config/crd/patches/webhook_in_recordings.yaml | 14 - ...yostat-operator.clusterserviceversion.yaml | 192 +----- config/manifests/kustomization.yaml | 7 - config/manifests/xdescriptors_patch.yaml | 7 - config/rbac/flightrecorder_editor_role.yaml | 24 - config/rbac/flightrecorder_viewer_role.yaml | 20 - config/rbac/recording_editor_role.yaml | 24 - config/rbac/recording_viewer_role.yaml | 20 - config/rbac/role.yaml | 74 --- config/samples/kustomization.yaml | 2 - .../operator_v1beta1_flightrecorder.yaml | 8 - .../samples/operator_v1beta1_recording.yaml | 12 - docs/api.md | 289 -------- internal/controllers/client/command_types.go | 87 --- .../controllers/client/cryostat_httpclient.go | 387 ----------- .../controllers/common/common_reconciler.go | 258 ------- internal/controllers/common/common_utils.go | 13 +- internal/controllers/endpoints_controller.go | 269 -------- .../controllers/endpoints_controller_test.go | 222 ------- .../controllers/flightrecorder_controller.go | 156 ----- .../flightrecorder_controller_test.go | 321 --------- internal/controllers/recording_controller.go | 543 --------------- .../controllers/recording_controller_test.go | 629 ------------------ internal/main.go | 33 - internal/test/handlers.go | 349 ---------- internal/test/reconciler.go | 40 -- internal/test/resources.go | 337 ---------- internal/test/server.go | 117 ---- 46 files changed, 132 insertions(+), 6210 deletions(-) delete mode 100644 api/v1beta1/flightrecorder_types.go delete mode 100644 api/v1beta1/recording_types.go delete mode 100644 bundle/manifests/operator.cryostat.io_flightrecorders.yaml delete mode 100644 bundle/manifests/operator.cryostat.io_recordings.yaml delete mode 100644 config/crd/bases/operator.cryostat.io_flightrecorders.yaml delete mode 100644 config/crd/bases/operator.cryostat.io_recordings.yaml delete mode 100644 config/crd/patches/cainjection_in_flightrecorders.yaml delete mode 100644 config/crd/patches/cainjection_in_recordings.yaml delete mode 100644 config/crd/patches/webhook_in_flightrecorders.yaml delete mode 100644 config/crd/patches/webhook_in_recordings.yaml delete mode 100644 config/manifests/xdescriptors_patch.yaml delete mode 100644 config/rbac/flightrecorder_editor_role.yaml delete mode 100644 config/rbac/flightrecorder_viewer_role.yaml delete mode 100644 config/rbac/recording_editor_role.yaml delete mode 100644 config/rbac/recording_viewer_role.yaml delete mode 100644 config/samples/operator_v1beta1_flightrecorder.yaml delete mode 100644 config/samples/operator_v1beta1_recording.yaml delete mode 100644 docs/api.md delete mode 100644 internal/controllers/client/command_types.go delete mode 100644 internal/controllers/client/cryostat_httpclient.go delete mode 100644 internal/controllers/common/common_reconciler.go delete mode 100644 internal/controllers/endpoints_controller.go delete mode 100644 internal/controllers/endpoints_controller_test.go delete mode 100644 internal/controllers/flightrecorder_controller.go delete mode 100644 internal/controllers/flightrecorder_controller_test.go delete mode 100644 internal/controllers/recording_controller.go delete mode 100644 internal/controllers/recording_controller_test.go delete mode 100644 internal/test/handlers.go delete mode 100644 internal/test/server.go diff --git a/Makefile b/Makefile index 0effd987..8d0b8812 100644 --- a/Makefile +++ b/Makefile @@ -164,10 +164,9 @@ ifeq ($(DISABLE_SERVICE_TLS), true) @$(CLUSTER_CLIENT) -n $(DEPLOY_NAMESPACE) set env deployment/cryostat-operator-controller-manager DISABLE_SERVICE_TLS=true endif -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config +# Undeploy controller from the configured Kubernetes cluster in ~/.kube/config .PHONY: undeploy undeploy: - - $(CLUSTER_CLIENT) delete --ignore-not-found=$(ignore-not-found) recording --all - $(CLUSTER_CLIENT) delete --ignore-not-found=$(ignore-not-found) -f config/samples/operator_v1beta1_cryostat.yaml - $(KUSTOMIZE) build config/default | $(CLUSTER_CLIENT) delete --ignore-not-found=$(ignore-not-found) -f - diff --git a/PROJECT b/PROJECT index e4ef394d..9c0cf4da 100644 --- a/PROJECT +++ b/PROJECT @@ -12,24 +12,6 @@ resources: kind: Cryostat path: github.com/cryostatio/cryostat-operator/api/v1beta1 version: v1beta1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: cryostat.io - group: operator - kind: FlightRecorder - path: github.com/cryostatio/cryostat-operator/api/v1beta1 - version: v1beta1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: cryostat.io - group: operator - kind: Recording - path: github.com/cryostatio/cryostat-operator/api/v1beta1 - version: v1beta1 version: "3" plugins: manifests.sdk.operatorframework.io/v2: {} diff --git a/README.md b/README.md index 26fcb67e..61343364 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ [![Google Group : Cryostat Development](https://img.shields.io/badge/Google%20Group-Cryostat%20Development-blue.svg)](https://groups.google.com/g/cryostat-development) A Kubernetes Operator to automate deployment of -[Cryostat](https://github.com/cryostatio/cryostat) and provide an -API to manage [JDK Flight Recordings](https://openjdk.java.net/jeps/328). - +[Cryostat](https://github.com/cryostatio/cryostat). ## SEE ALSO * [cryostat-core](https://github.com/cryostatio/cryostat-core) for diff --git a/api/v1beta1/cryostat_types.go b/api/v1beta1/cryostat_types.go index cd98313f..d38d44ad 100644 --- a/api/v1beta1/cryostat_types.go +++ b/api/v1beta1/cryostat_types.go @@ -42,16 +42,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// CryostatSpec defines the desired state of Cryostat +// CryostatSpec defines the desired state of Cryostat. type CryostatSpec struct { // Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Minimal Deployment",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} Minimal bool `json:"minimal"` - // List of TLS certificates to trust when connecting to targets + // List of TLS certificates to trust when connecting to targets. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Trusted TLS Certificates" TrustedCertSecrets []CertificateSecret `json:"trustedCertSecrets,omitempty"` - // List of Flight Recorder Event Templates to preconfigure in Cryostat + // List of Flight Recorder Event Templates to preconfigure in Cryostat. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Event Templates" EventTemplates []TemplateConfigMap `json:"eventTemplates,omitempty"` @@ -60,7 +60,7 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Enable cert-manager Integration",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} EnableCertManager *bool `json:"enableCertManager"` - // Options to customize the storage for Flight Recordings and Templates + // Options to customize the storage for Flight Recordings and Templates. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec StorageOptions *StorageConfiguration `json:"storageOptions,omitempty"` @@ -73,20 +73,20 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec NetworkOptions *NetworkConfigurationList `json:"networkOptions,omitempty"` - // Options to configure Cryostat Automated Report Analysis + // Options to configure Cryostat Automated Report Analysis. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec ReportOptions *ReportConfiguration `json:"reportOptions,omitempty"` - // The maximum number of WebSocket client connections allowed (minimum 1, default unlimited) + // The maximum number of WebSocket client connections allowed (minimum 1, default unlimited). // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Max WebSocket Connections",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} // +kubebuilder:validation:Minimum=1 MaxWsConnections int32 `json:"maxWsConnections,omitempty"` - // Options to customize the JMX target connections cache for the Cryostat application + // Options to customize the JMX target connections cache for the Cryostat application. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="JMX Connections Cache Options" JmxCacheOptions *JmxCacheOptions `json:"jmxCacheOptions,omitempty"` - // Resource requirements for the Cryostat deployment + // Resource requirements for the Cryostat deployment. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec Resources ResourceConfigList `json:"resources,omitempty"` @@ -101,27 +101,27 @@ type ResourceConfigList struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:resourceRequirements"} CoreResources corev1.ResourceRequirements `json:"coreResources,omitempty"` - // Resource requirements for the JFR Data Source container + // Resource requirements for the JFR Data Source container. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:resourceRequirements"} DataSourceResources corev1.ResourceRequirements `json:"dataSourceResources,omitempty"` - // Resource requirements for the Grafana container + // Resource requirements for the Grafana container. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:resourceRequirements"} GrafanaResources corev1.ResourceRequirements `json:"grafanaResources,omitempty"` } -// CryostatStatus defines the observed state of Cryostat +// CryostatStatus defines the observed state of Cryostat. type CryostatStatus struct { - // Conditions of the components managed by the Cryostat Operator + // Conditions of the components managed by the Cryostat Operator. // +optional // +operator-sdk:csv:customresourcedefinitions:type=status,displayName="Cryostat Conditions",xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} Conditions []metav1.Condition `json:"conditions,omitempty"` - // Name of the Secret containing the generated Grafana credentials + // Name of the Secret containing the generated Grafana credentials. // +optional // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} GrafanaSecret string `json:"grafanaSecret,omitempty"` - // Address of the deployed Cryostat web application + // Address of the deployed Cryostat web application. // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:org.w3:link"} ApplicationURL string `json:"applicationUrl"` } @@ -130,19 +130,19 @@ type CryostatStatus struct { type CryostatConditionType string const ( - // Whether the main Cryostat deployment is available + // Whether the main Cryostat deployment is available. ConditionTypeMainDeploymentAvailable CryostatConditionType = "MainDeploymentAvailable" - // Whether the main Cryostat deployment is progressing + // Whether the main Cryostat deployment is progressing. ConditionTypeMainDeploymentProgressing CryostatConditionType = "MainDeploymentProgressing" - // If pods within the main Cryostat deployment failed to be created or destroyed + // If pods within the main Cryostat deployment failed to be created or destroyed. ConditionTypeMainDeploymentReplicaFailure CryostatConditionType = "MainDeploymentReplicaFailure" - // If enabled, whether the reports deployment is available + // If enabled, whether the reports deployment is available. ConditionTypeReportsDeploymentAvailable CryostatConditionType = "ReportsDeploymentAvailable" - // If enabled, whether the reports deployment is progressing + // If enabled, whether the reports deployment is progressing. ConditionTypeReportsDeploymentProgressing CryostatConditionType = "ReportsDeploymentProgressing" - // If enabled, whether pods in the reports deployment failed to be created or destroyed + // If enabled, whether pods in the reports deployment failed to be created or destroyed. ConditionTypeReportsDeploymentReplicaFailure CryostatConditionType = "ReportsDeploymentReplicaFailure" - // If enabled, whether TLS setup is complete for the Cryostat components + // If enabled, whether TLS setup is complete for the Cryostat components. ConditionTypeTLSSetupComplete CryostatConditionType = "TLSSetupComplete" ) @@ -204,7 +204,7 @@ type ServiceConfig struct { } // CoreServiceConfig provides customization for the service handling -// traffic for the Cryostat application +// traffic for the Cryostat application. type CoreServiceConfig struct { // HTTP port number for the Cryostat application service. // Defaults to 8181. @@ -218,7 +218,7 @@ type CoreServiceConfig struct { } // GrafanaServiceConfig provides customization for the service handling -// traffic for the Grafana dashboard +// traffic for the Grafana dashboard. type GrafanaServiceConfig struct { // HTTP port number for the Grafana dashboard service. // Defaults to 3000. @@ -228,7 +228,7 @@ type GrafanaServiceConfig struct { } // ReportsServiceConfig provides customization for the service handling -// traffic for the cryostat-reports sidecars +// traffic for the cryostat-reports sidecars. type ReportsServiceConfig struct { // HTTP port number for the cryostat-reports service. // Defaults to 10000. @@ -258,7 +258,7 @@ type ServiceConfigList struct { type NetworkConfiguration struct { // Configuration for an Ingress object. // Currently subpaths are not supported, so unique hosts must be specified - // (if a single external IP is being used) to differentiate between ingresses/services + // (if a single external IP is being used) to differentiate between ingresses/services. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec IngressSpec *netv1.IngressSpec `json:"ingressSpec,omitempty"` @@ -275,22 +275,22 @@ type NetworkConfiguration struct { // NetworkConfigurationList holds NetworkConfiguration objects that specify // how to expose the services created by the operator for the main Cryostat -// deployment +// deployment. type NetworkConfigurationList struct { // Specifications for how to expose the Cryostat service, - // which serves the Cryostat application + // which serves the Cryostat application. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec CoreConfig *NetworkConfiguration `json:"coreConfig,omitempty"` // Specifications for how to expose the Cryostat command service, - // which serves the WebSocket command channel + // which serves the WebSocket command channel. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} // // Deprecated: CommandConfig is no longer used. CommandConfig *NetworkConfiguration `json:"commandConfig,omitempty"` // Specifications for how to expose Cryostat's Grafana service, - // which serves the Grafana dashboard + // which serves the Grafana dashboard. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec GrafanaConfig *NetworkConfiguration `json:"grafanaConfig,omitempty"` @@ -338,7 +338,7 @@ type EmptyDirConfig struct { } // JmxCacheConfig provides customization for the JMX target connections -// cache for the Cryostat application +// cache for the Cryostat application. type JmxCacheOptions struct { // The maximum number of JMX connections to cache. Use `-1` for an unlimited cache size (TTL expiration only). Defaults to `-1`. // +optional @@ -360,7 +360,7 @@ type JmxCacheOptions struct { // Cryostat contains configuration options for controlling the Deployment of // the Cryostat application and its related components. A Cryostat instance // must be created to instruct the operator to deploy the Cryostat application. -//+operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} +// +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} // +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` // +kubebuilder:printcolumn:name="Grafana Secret",type=string,JSONPath=`.status.grafanaSecret` type Cryostat struct { @@ -385,24 +385,24 @@ func init() { } // DefaultCertificateKey will be used when looking up the certificate within a secret, -// if a key is not manually specified +// if a key is not manually specified. const DefaultCertificateKey = corev1.TLSCertKey type CertificateSecret struct { - // Name of secret in the local namespace + // Name of secret in the local namespace. // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} SecretName string `json:"secretName"` - // Key within secret containing the certificate + // Key within secret containing the certificate. // +optional CertificateKey *string `json:"certificateKey,omitempty"` } -// A ConfigMap containing a .jfc template file +// A ConfigMap containing a .jfc template file. type TemplateConfigMap struct { - // Name of config map in the local namespace + // Name of config map in the local namespace. // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:ConfigMap"} ConfigMapName string `json:"configMapName"` - // Filename within config map containing the template file + // Filename within config map containing the template file. Filename string `json:"filename"` } diff --git a/api/v1beta1/flightrecorder_types.go b/api/v1beta1/flightrecorder_types.go deleted file mode 100644 index 509b4423..00000000 --- a/api/v1beta1/flightrecorder_types.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package v1beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// FlightRecorderSpec defines the desired state of FlightRecorder -type FlightRecorderSpec struct { - // Recordings that match this selector belong to this FlightRecorder - // +operator-sdk:csv:customresourcedefinitions:type=spec - RecordingSelector *metav1.LabelSelector `json:"recordingSelector"` - // If JMX authentication is enabled for this FlightRecorder's JVM, specify the credentials in a secret - // and reference it here - // +optional - // +operator-sdk:csv:customresourcedefinitions:type=spec - JMXCredentials *JMXAuthSecret `json:"jmxCredentials,omitempty"` -} - -// FlightRecorderStatus defines the observed state of FlightRecorder -type FlightRecorderStatus struct { - // Listing of events available in the target JVM - // +operator-sdk:csv:customresourcedefinitions:type=status - // +listType=atomic - Events []EventInfo `json:"events"` - // Listing of templates available in the target JVM - // +operator-sdk:csv:customresourcedefinitions:type=status - // +listType=atomic - Templates []TemplateInfo `json:"templates"` - // Reference to the pod/service that this object controls JFR for - // +operator-sdk:csv:customresourcedefinitions:type=status - Target *corev1.ObjectReference `json:"target"` - // JMX port for target JVM - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:text"} - // +kubebuilder:validation:Minimum=0 - Port int32 `json:"port"` -} - -// RecordingLabel is the label name to be used with FlightRecorderSpec.RecordingSelector -const RecordingLabel = "operator.cryostat.io/flightrecorder" - -// EventInfo contains metadata for a JFR event type -type EventInfo struct { - // The ID used by JFR to uniquely identify this event type - TypeID string `json:"typeId"` - // Human-readable name for this type of event - Name string `json:"name"` - // A description detailing what this event does - Description string `json:"description"` - // A hierarchical category used to organize related event types - // +listType=atomic - Category []string `json:"category"` - // Options that may be used to tune this event. This map is indexed - // by the option IDs. - Options map[string]OptionDescriptor `json:"options"` -} - -// TemplateInfo contains metadata for a JFR template -type TemplateInfo struct { - // The name of the template - Name string `json:"name"` - // A description of the template and its performance impact - Description string `json:"description"` - // The organization which has provided the template - Provider string `json:"provider"` - // The type of template, which is either "TARGET" for built-in templates, - // or "CUSTOM" for user created templates - // +kubebuilder:validation:Enum=TARGET;CUSTOM - Type TemplateType `json:"type"` -} - -type TemplateType string - -const ( - // TemplateTypeTarget means the template is provided by the target JVM - TemplateTypeTarget TemplateType = "TARGET" - // TemplateTypeCustom means the template is created by the user - TemplateTypeCustom TemplateType = "CUSTOM" -) - -// OptionDescriptor contains metadata for an option for a particular event type -type OptionDescriptor struct { - // Human-readable name for this option - Name string `json:"name"` - // A description of what this option does - Description string `json:"description"` - // The value implicitly used when this option isn't specified - DefaultValue string `json:"defaultValue"` -} - -// DefaultUsernameKey will be used when looking up the username within a JMX auth secret, -// if a key is not manually specified -const DefaultUsernameKey = corev1.BasicAuthUsernameKey - -// DefaultPasswordKey will be used when looking up the password within a JMX auth secret, -// if a key is not manually specified -const DefaultPasswordKey = corev1.BasicAuthPasswordKey - -// JMXAuthSecret references a secret containing JMX authentication credentials -// for the FlightRecorder's JVM -type JMXAuthSecret struct { - // Name of secret in the local namespace - SecretName string `json:"secretName"` - // Key within secret containing the username, defaults to DefaultUsernameKey - // +optional - UsernameKey *string `json:"usernameKey,omitempty"` - // Key within secret containing the password, defaults to DefaultPasswordKey - // +optional - PasswordKey *string `json:"passwordKey,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// +k8s:openapi-gen=true -// +kubebuilder:resource:path=flightrecorders,scope=Namespaced -// +kubebuilder:storageversion -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:deprecatedversion:warning="operator.cryostat.io/v1beta1 FlightRecorder is deprecated; please use the Cryostat web application or the Cryostat HTTP API to manage recordings" - -// FlightRecorder represents a target Pod that is capable of creating JDK Flight Recordings -// using Cryostat. The Cryostat operator creates FlightRecorder objects when it finds -// compatible Pods. -//+operator-sdk:csv:customresourcedefinitions:resources={{Pod,v1},{Secret,v1},{Service,v1}} -type FlightRecorder struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec FlightRecorderSpec `json:"spec,omitempty"` - Status FlightRecorderStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:object:root=true - -// FlightRecorderList contains a list of FlightRecorder -type FlightRecorderList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []FlightRecorder `json:"items"` -} - -func init() { - SchemeBuilder.Register(&FlightRecorder{}, &FlightRecorderList{}) -} diff --git a/api/v1beta1/recording_types.go b/api/v1beta1/recording_types.go deleted file mode 100644 index 52c5d7fe..00000000 --- a/api/v1beta1/recording_types.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package v1beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// RecordingSpec defines the desired state of Recording -type RecordingSpec struct { - // Name of the recording to be created. - // +operator-sdk:csv:customresourcedefinitions:type=spec - Name string `json:"name"` - // Name of the event template to use when creating the recording. Must be prefixed with "template=". - // e.g. template=Profiling - // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Event Template",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} - // +listType=atomic - EventOptions []string `json:"eventOptions"` - // The requested total duration of the recording, a zero value will record indefinitely. - // The duration format is a combination of hours (h), minutes (m) and seconds (s). e.g. 30s, 0s, 1h30m - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} - Duration metav1.Duration `json:"duration"` - // Desired state of the recording. If omitted, RUNNING will be assumed. - // +kubebuilder:validation:Enum=RUNNING;STOPPED - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:select:RUNNING","urn:alm:descriptor:com.tectonic.ui:select:STOPPED"} - // +optional - State *RecordingState `json:"state,omitempty"` - // Whether this recording should be saved to persistent storage. If true, the JFR file will be retained until - // this object is deleted. If false, the JFR file will be deleted when its corresponding JVM exits. - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:checkbox"} - Archive bool `json:"archive"` - // Reference to the FlightRecorder object that corresponds to this Recording. - // +operator-sdk:csv:customresourcedefinitions:type=spec - FlightRecorder *corev1.LocalObjectReference `json:"flightRecorder"` -} - -// RecordingState describes the current state of the recording according -// to JFR -type RecordingState string - -const ( - // RecordingStateCreated means the recording has been accepted, but - // has not started yet. - RecordingStateCreated RecordingState = "CREATED" - // RecordingStateRunning means the recording has started and is - // currently running. - RecordingStateRunning RecordingState = "RUNNING" - // RecordingStateStopping means that the recording is in the process - // of finishing. - RecordingStateStopping RecordingState = "STOPPING" - // RecordingStateStopped means the recording has completed and the - // JFR file is fully written. - RecordingStateStopped RecordingState = "STOPPED" -) - -// RecordingStatus defines the observed state of Recording -type RecordingStatus struct { - // Current state of the recording. - // +kubebuilder:validation:Enum=CREATED;RUNNING;STOPPING;STOPPED - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:text"} - // +optional - State *RecordingState `json:"state,omitempty"` - // The date/time when the recording started. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:text"} - // +optional - StartTime metav1.Time `json:"startTime,omitempty"` - // The duration of the recording specified during creation. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:text"} - // +optional - Duration metav1.Duration `json:"duration,omitempty"` - // A URL to download the JFR file for the recording. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:org.w3:link"} - // +optional - DownloadURL *string `json:"downloadURL,omitempty"` - // A URL to download the autogenerated HTML report for the recording - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:org.w3:link"} - // +optional - ReportURL *string `json:"reportURL,omitempty"` - // TODO Consider adding Conditions: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:storageversion -// +kubebuilder:resource:path=recordings,scope=Namespaced -// +kubebuilder:deprecatedversion:warning="operator.cryostat.io/v1beta1 Recording is deprecated; please use the Cryostat web application or the Cryostat HTTP API to manage recordings" - -// Recording represents a JDK Flight Recording. Create a Recording object to instruct Cryostat to start a new -// recording for a Pod. An alternative to managing recordings with the Cryostat web application. -//+operator-sdk:csv:customresourcedefinitions:resources={{Pod,v1},{Secret,v1},{Service,v1}} -type Recording struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RecordingSpec `json:"spec,omitempty"` - Status RecordingStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// RecordingList contains a list of Recording -type RecordingList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Recording `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Recording{}, &RecordingList{}) -} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 51a99e17..e0a05165 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -268,149 +268,6 @@ func (in *EmptyDirConfig) DeepCopy() *EmptyDirConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventInfo) DeepCopyInto(out *EventInfo) { - *out = *in - if in.Category != nil { - in, out := &in.Category, &out.Category - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Options != nil { - in, out := &in.Options, &out.Options - *out = make(map[string]OptionDescriptor, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventInfo. -func (in *EventInfo) DeepCopy() *EventInfo { - if in == nil { - return nil - } - out := new(EventInfo) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlightRecorder) DeepCopyInto(out *FlightRecorder) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlightRecorder. -func (in *FlightRecorder) DeepCopy() *FlightRecorder { - if in == nil { - return nil - } - out := new(FlightRecorder) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FlightRecorder) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlightRecorderList) DeepCopyInto(out *FlightRecorderList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]FlightRecorder, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlightRecorderList. -func (in *FlightRecorderList) DeepCopy() *FlightRecorderList { - if in == nil { - return nil - } - out := new(FlightRecorderList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FlightRecorderList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlightRecorderSpec) DeepCopyInto(out *FlightRecorderSpec) { - *out = *in - if in.RecordingSelector != nil { - in, out := &in.RecordingSelector, &out.RecordingSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.JMXCredentials != nil { - in, out := &in.JMXCredentials, &out.JMXCredentials - *out = new(JMXAuthSecret) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlightRecorderSpec. -func (in *FlightRecorderSpec) DeepCopy() *FlightRecorderSpec { - if in == nil { - return nil - } - out := new(FlightRecorderSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlightRecorderStatus) DeepCopyInto(out *FlightRecorderStatus) { - *out = *in - if in.Events != nil { - in, out := &in.Events, &out.Events - *out = make([]EventInfo, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Templates != nil { - in, out := &in.Templates, &out.Templates - *out = make([]TemplateInfo, len(*in)) - copy(*out, *in) - } - if in.Target != nil { - in, out := &in.Target, &out.Target - *out = new(corev1.ObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlightRecorderStatus. -func (in *FlightRecorderStatus) DeepCopy() *FlightRecorderStatus { - if in == nil { - return nil - } - out := new(FlightRecorderStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GrafanaServiceConfig) DeepCopyInto(out *GrafanaServiceConfig) { *out = *in @@ -432,31 +289,6 @@ func (in *GrafanaServiceConfig) DeepCopy() *GrafanaServiceConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JMXAuthSecret) DeepCopyInto(out *JMXAuthSecret) { - *out = *in - if in.UsernameKey != nil { - in, out := &in.UsernameKey, &out.UsernameKey - *out = new(string) - **out = **in - } - if in.PasswordKey != nil { - in, out := &in.PasswordKey, &out.PasswordKey - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JMXAuthSecret. -func (in *JMXAuthSecret) DeepCopy() *JMXAuthSecret { - if in == nil { - return nil - } - out := new(JMXAuthSecret) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JmxCacheOptions) DeepCopyInto(out *JmxCacheOptions) { *out = *in @@ -536,21 +368,6 @@ func (in *NetworkConfigurationList) DeepCopy() *NetworkConfigurationList { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OptionDescriptor) DeepCopyInto(out *OptionDescriptor) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionDescriptor. -func (in *OptionDescriptor) DeepCopy() *OptionDescriptor { - if in == nil { - return nil - } - out := new(OptionDescriptor) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PersistentVolumeClaimConfig) DeepCopyInto(out *PersistentVolumeClaimConfig) { *out = *in @@ -585,128 +402,6 @@ func (in *PersistentVolumeClaimConfig) DeepCopy() *PersistentVolumeClaimConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Recording) DeepCopyInto(out *Recording) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Recording. -func (in *Recording) DeepCopy() *Recording { - if in == nil { - return nil - } - out := new(Recording) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Recording) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RecordingList) DeepCopyInto(out *RecordingList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Recording, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecordingList. -func (in *RecordingList) DeepCopy() *RecordingList { - if in == nil { - return nil - } - out := new(RecordingList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RecordingList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RecordingSpec) DeepCopyInto(out *RecordingSpec) { - *out = *in - if in.EventOptions != nil { - in, out := &in.EventOptions, &out.EventOptions - *out = make([]string, len(*in)) - copy(*out, *in) - } - out.Duration = in.Duration - if in.State != nil { - in, out := &in.State, &out.State - *out = new(RecordingState) - **out = **in - } - if in.FlightRecorder != nil { - in, out := &in.FlightRecorder, &out.FlightRecorder - *out = new(corev1.LocalObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecordingSpec. -func (in *RecordingSpec) DeepCopy() *RecordingSpec { - if in == nil { - return nil - } - out := new(RecordingSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RecordingStatus) DeepCopyInto(out *RecordingStatus) { - *out = *in - if in.State != nil { - in, out := &in.State, &out.State - *out = new(RecordingState) - **out = **in - } - in.StartTime.DeepCopyInto(&out.StartTime) - out.Duration = in.Duration - if in.DownloadURL != nil { - in, out := &in.DownloadURL, &out.DownloadURL - *out = new(string) - **out = **in - } - if in.ReportURL != nil { - in, out := &in.ReportURL, &out.ReportURL - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecordingStatus. -func (in *RecordingStatus) DeepCopy() *RecordingStatus { - if in == nil { - return nil - } - out := new(RecordingStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReportConfiguration) DeepCopyInto(out *ReportConfiguration) { *out = *in @@ -865,18 +560,3 @@ func (in *TemplateConfigMap) DeepCopy() *TemplateConfigMap { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TemplateInfo) DeepCopyInto(out *TemplateInfo) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateInfo. -func (in *TemplateInfo) DeepCopy() *TemplateInfo { - if in == nil { - return nil - } - out := new(TemplateInfo) - in.DeepCopyInto(out) - return out -} diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 5814676d..f4e2a86c 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -26,38 +26,6 @@ metadata: }, "trustedCertSecrets": [] } - }, - { - "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "FlightRecorder", - "metadata": { - "name": "example-flightrecorder" - }, - "spec": { - "recordingSelector": { - "matchLabels": { - "operator.cryostat.io/flightrecorder": "example-flightrecorder" - } - } - } - }, - { - "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "Recording", - "metadata": { - "name": "example-recording" - }, - "spec": { - "archive": true, - "duration": "30s", - "eventOptions": [ - "template=ALL" - ], - "flightRecorder": { - "name": "example-flightrecorder" - }, - "name": "example-recording" - } } ] capabilities: Basic Install @@ -81,7 +49,6 @@ metadata: } } operators.operatorframework.io/builder: operator-sdk-v1.22.2 - operators.operatorframework.io/internal-objects: '["flightrecorders.operator.cryostat.io","recordings.operator.cryostat.io"]' operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: github.com/cryostatio/cryostat-operator support: Cryostat Community @@ -148,16 +115,16 @@ spec: path: enableCertManager x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: List of Flight Recorder Event Templates to preconfigure in Cryostat + - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates - - description: Name of config map in the local namespace + - description: Name of config map in the local namespace. displayName: Config Map Name path: eventTemplates[0].configMapName x-descriptors: - urn:alm:descriptor:io.kubernetes:ConfigMap - description: Options to customize the JMX target connections cache for the - Cryostat application + Cryostat application. displayName: JMX Connections Cache Options path: jmxCacheOptions - description: The maximum number of JMX connections to cache. Use `-1` for @@ -173,7 +140,7 @@ spec: x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited) + 1, default unlimited). displayName: Max WebSocket Connections path: maxWsConnections x-descriptors: @@ -189,7 +156,7 @@ spec: displayName: Network Options path: networkOptions - description: "Specifications for how to expose the Cryostat command service, - which serves the WebSocket command channel \n Deprecated: CommandConfig + which serves the WebSocket command channel. \n Deprecated: CommandConfig is no longer used." displayName: Command Config path: networkOptions.commandConfig @@ -200,7 +167,7 @@ spec: path: networkOptions.commandConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.commandConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The @@ -208,7 +175,7 @@ spec: displayName: Labels path: networkOptions.commandConfig.labels - description: Specifications for how to expose the Cryostat service, which - serves the Cryostat application + serves the Cryostat application. displayName: Core Config path: networkOptions.coreConfig - description: Annotations to add to the Ingress or Route during its creation. @@ -216,7 +183,7 @@ spec: path: networkOptions.coreConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.coreConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The @@ -224,7 +191,7 @@ spec: displayName: Labels path: networkOptions.coreConfig.labels - description: Specifications for how to expose Cryostat's Grafana service, - which serves the Grafana dashboard + which serves the Grafana dashboard. displayName: Grafana Config path: networkOptions.grafanaConfig - description: Annotations to add to the Ingress or Route during its creation. @@ -232,14 +199,14 @@ spec: path: networkOptions.grafanaConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.grafanaConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels - - description: Options to configure Cryostat Automated Report Analysis + - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions - description: The number of report sidecar replica containers to deploy. Each @@ -262,7 +229,7 @@ spec: path: reportOptions.subProcessMaxHeapSize x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Resource requirements for the Cryostat deployment + - description: Resource requirements for the Cryostat deployment. displayName: Resources path: resources - description: Resource requirements for the Cryostat application. If specifying @@ -271,12 +238,12 @@ spec: path: resources.coreResources x-descriptors: - urn:alm:descriptor:com.tectonic.ui:resourceRequirements - - description: Resource requirements for the JFR Data Source container + - description: Resource requirements for the JFR Data Source container. displayName: Data Source Resources path: resources.dataSourceResources x-descriptors: - urn:alm:descriptor:com.tectonic.ui:resourceRequirements - - description: Resource requirements for the Grafana container + - description: Resource requirements for the Grafana container. displayName: Grafana Resources path: resources.grafanaResources x-descriptors: @@ -285,7 +252,7 @@ spec: and Grafana dashboard. displayName: Service Options path: serviceOptions - - description: Options to customize the storage for Flight Recordings and Templates + - description: Options to customize the storage for Flight Recordings and Templates. displayName: Storage Options path: storageOptions - description: Configuration for an EmptyDir to be created by the operator instead @@ -320,154 +287,31 @@ spec: has created the PVC, changes to this field have no effect. displayName: Spec path: storageOptions.pvc.spec - - description: List of TLS certificates to trust when connecting to targets + - description: List of TLS certificates to trust when connecting to targets. displayName: Trusted TLS Certificates path: trustedCertSecrets - - description: Name of secret in the local namespace + - description: Name of secret in the local namespace. displayName: Secret Name path: trustedCertSecrets[0].secretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret statusDescriptors: - - description: Address of the deployed Cryostat web application + - description: Address of the deployed Cryostat web application. displayName: Application URL path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator + - description: Conditions of the components managed by the Cryostat Operator. displayName: Cryostat Conditions path: conditions x-descriptors: - urn:alm:descriptor:io.kubernetes.conditions - - description: Name of the Secret containing the generated Grafana credentials + - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret version: v1beta1 - - description: FlightRecorder represents a target Pod that is capable of creating - JDK Flight Recordings using Cryostat. The Cryostat operator creates FlightRecorder - objects when it finds compatible Pods. - displayName: Flight Recorder - kind: FlightRecorder - name: flightrecorders.operator.cryostat.io - resources: - - kind: Pod - name: "" - version: v1 - - kind: Secret - name: "" - version: v1 - - kind: Service - name: "" - version: v1 - specDescriptors: - - description: If JMX authentication is enabled for this FlightRecorder's JVM, - specify the credentials in a secret and reference it here - displayName: JMXCredentials - path: jmxCredentials - - description: Recordings that match this selector belong to this FlightRecorder - displayName: Recording Selector - path: recordingSelector - statusDescriptors: - - description: Listing of events available in the target JVM - displayName: Events - path: events - - description: JMX port for target JVM - displayName: Port - path: port - x-descriptors: - - urn:alm:descriptor:text - - description: Reference to the pod/service that this object controls JFR for - displayName: Target - path: target - - description: Listing of templates available in the target JVM - displayName: Templates - path: templates - version: v1beta1 - - description: Recording represents a JDK Flight Recording. Create a Recording - object to instruct Cryostat to start a new recording for a Pod. An alternative - to managing recordings with the Cryostat web application. - displayName: Recording - kind: Recording - name: recordings.operator.cryostat.io - resources: - - kind: Pod - name: "" - version: v1 - - kind: Secret - name: "" - version: v1 - - kind: Service - name: "" - version: v1 - specDescriptors: - - description: Whether this recording should be saved to persistent storage. - If true, the JFR file will be retained until this object is deleted. If - false, the JFR file will be deleted when its corresponding JVM exits. - displayName: Archive - path: archive - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:checkbox - - description: The requested total duration of the recording, a zero value will - record indefinitely. The duration format is a combination of hours (h), - minutes (m) and seconds (s). e.g. 30s, 0s, 1h30m - displayName: Duration - path: duration - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:text - - description: Name of the event template to use when creating the recording. - Must be prefixed with "template=". e.g. template=Profiling - displayName: Event Template - path: eventOptions - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:text - - description: Select the FlightRecorder with the name of the target Pod for - this Recording. - displayName: Name - path: flightRecorder.name - x-descriptors: - - urn:alm:descriptor:io.kubernetes:operator.cryostat.io:v1beta1:FlightRecorder - - description: Reference to the FlightRecorder object that corresponds to this - Recording. - displayName: Flight Recorder - path: flightRecorder - - description: Name of the recording to be created. - displayName: Name - path: name - - description: Desired state of the recording. If omitted, RUNNING will be assumed. - displayName: State - path: state - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:select:RUNNING - - urn:alm:descriptor:com.tectonic.ui:select:STOPPED - statusDescriptors: - - description: A URL to download the JFR file for the recording. - displayName: Download URL - path: downloadURL - x-descriptors: - - urn:alm:descriptor:org.w3:link - - description: The duration of the recording specified during creation. - displayName: Duration - path: duration - x-descriptors: - - urn:alm:descriptor:text - - description: A URL to download the autogenerated HTML report for the recording - displayName: Report URL - path: reportURL - x-descriptors: - - urn:alm:descriptor:org.w3:link - - description: The date/time when the recording started. - displayName: Start Time - path: startTime - x-descriptors: - - urn:alm:descriptor:text - - description: Current state of the recording. - displayName: State - path: state - x-descriptors: - - urn:alm:descriptor:text - version: v1beta1 description: | Cryostat provides a cloud-based solution for interacting with the JDK Flight Recorder already present in OpenJDK 11+ JVMs. With Cryostat, users can remotely start, stop, retrieve, and even analyze JFR event data, providing the capability to easily take advantage of Flight Recorder's extremely low runtime cost and overhead and the flexibility to monitor applications and analyze recording data without transferring data outside of the cluster the application runs within. ##Prerequisites @@ -649,31 +493,6 @@ spec: - services/finalizers verbs: - '*' - - apiGroups: - - "" - resources: - - endpoints - - pods - - secrets - - services - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - secrets - - services - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - "" resources: @@ -725,21 +544,6 @@ spec: - cryostats verbs: - '*' - - apiGroups: - - operator.cryostat.io - resources: - - cryostats - - flightrecorders - verbs: - - '*' - - apiGroups: - - operator.cryostat.io - resources: - - cryostats - - flightrecorders - - recordings - verbs: - - '*' - apiGroups: - operator.cryostat.io resources: @@ -754,40 +558,6 @@ spec: - get - patch - update - - apiGroups: - - operator.cryostat.io - resources: - - flightrecorders - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - - apiGroups: - - operator.cryostat.io - resources: - - flightrecorders/status - verbs: - - get - - patch - - update - - apiGroups: - - operator.cryostat.io - resources: - - recordings/finalizers - verbs: - - update - - apiGroups: - - operator.cryostat.io - resources: - - recordings/status - verbs: - - get - - patch - - update - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 9bbefed4..e5fef795 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -41,7 +41,7 @@ spec: metadata: type: object spec: - description: CryostatSpec defines the desired state of Cryostat + description: CryostatSpec defines the desired state of Cryostat. properties: authProperties: description: Override default authorization properties for Cryostat @@ -71,16 +71,16 @@ spec: type: boolean eventTemplates: description: List of Flight Recorder Event Templates to preconfigure - in Cryostat + in Cryostat. items: - description: A ConfigMap containing a .jfc template file + description: A ConfigMap containing a .jfc template file. properties: configMapName: - description: Name of config map in the local namespace + description: Name of config map in the local namespace. type: string filename: description: Filename within config map containing the template - file + file. type: string required: - configMapName @@ -89,7 +89,7 @@ spec: type: array jmxCacheOptions: description: Options to customize the JMX target connections cache - for the Cryostat application + for the Cryostat application. properties: targetCacheSize: description: The maximum number of JMX connections to cache. Use @@ -107,7 +107,7 @@ spec: type: object maxWsConnections: description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited) + (minimum 1, default unlimited). format: int32 minimum: 1 type: integer @@ -121,7 +121,7 @@ spec: properties: commandConfig: description: "Specifications for how to expose the Cryostat command - service, which serves the WebSocket command channel \n Deprecated: + service, which serves the WebSocket command channel. \n Deprecated: CommandConfig is no longer used." properties: annotations: @@ -134,7 +134,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -433,7 +433,7 @@ spec: type: object coreConfig: description: Specifications for how to expose the Cryostat service, - which serves the Cryostat application + which serves the Cryostat application. properties: annotations: additionalProperties: @@ -445,7 +445,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -744,7 +744,7 @@ spec: type: object grafanaConfig: description: Specifications for how to expose Cryostat's Grafana - service, which serves the Grafana dashboard + service, which serves the Grafana dashboard. properties: annotations: additionalProperties: @@ -756,7 +756,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -1055,7 +1055,7 @@ spec: type: object type: object reportOptions: - description: Options to configure Cryostat Automated Report Analysis + description: Options to configure Cryostat Automated Report Analysis. properties: replicas: description: The number of report sidecar replica containers to @@ -1100,7 +1100,7 @@ spec: type: integer type: object resources: - description: Resource requirements for the Cryostat deployment + description: Resource requirements for the Cryostat deployment. properties: coreResources: description: Resource requirements for the Cryostat application. @@ -1130,7 +1130,7 @@ spec: type: object type: object dataSourceResources: - description: Resource requirements for the JFR Data Source container + description: Resource requirements for the JFR Data Source container. properties: limits: additionalProperties: @@ -1156,7 +1156,7 @@ spec: type: object type: object grafanaResources: - description: Resource requirements for the Grafana container + description: Resource requirements for the Grafana container. properties: limits: additionalProperties: @@ -1272,7 +1272,7 @@ spec: type: object storageOptions: description: Options to customize the storage for Flight Recordings - and Templates + and Templates. properties: emptyDir: description: Configuration for an EmptyDir to be created by the @@ -1486,14 +1486,14 @@ spec: type: object trustedCertSecrets: description: List of TLS certificates to trust when connecting to - targets + targets. items: properties: certificateKey: - description: Key within secret containing the certificate + description: Key within secret containing the certificate. type: string secretName: - description: Name of secret in the local namespace + description: Name of secret in the local namespace. type: string required: - secretName @@ -1503,14 +1503,14 @@ spec: - minimal type: object status: - description: CryostatStatus defines the observed state of Cryostat + description: CryostatStatus defines the observed state of Cryostat. properties: applicationUrl: - description: Address of the deployed Cryostat web application + description: Address of the deployed Cryostat web application. type: string conditions: description: Conditions of the components managed by the Cryostat - Operator + Operator. items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct @@ -1579,7 +1579,7 @@ spec: type: object type: array grafanaSecret: - description: Name of the Secret containing the generated Grafana credentials + description: Name of the Secret containing the generated Grafana credentials. type: string required: - applicationUrl diff --git a/bundle/manifests/operator.cryostat.io_flightrecorders.yaml b/bundle/manifests/operator.cryostat.io_flightrecorders.yaml deleted file mode 100644 index 8513c52a..00000000 --- a/bundle/manifests/operator.cryostat.io_flightrecorders.yaml +++ /dev/null @@ -1,252 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.0 - creationTimestamp: null - name: flightrecorders.operator.cryostat.io -spec: - group: operator.cryostat.io - names: - kind: FlightRecorder - listKind: FlightRecorderList - plural: flightrecorders - singular: flightrecorder - scope: Namespaced - versions: - - deprecated: true - deprecationWarning: operator.cryostat.io/v1beta1 FlightRecorder is deprecated; - please use the Cryostat web application or the Cryostat HTTP API to manage recordings - name: v1beta1 - schema: - openAPIV3Schema: - description: FlightRecorder represents a target Pod that is capable of creating - JDK Flight Recordings using Cryostat. The Cryostat operator creates FlightRecorder - objects when it finds compatible Pods. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: FlightRecorderSpec defines the desired state of FlightRecorder - properties: - jmxCredentials: - description: If JMX authentication is enabled for this FlightRecorder's - JVM, specify the credentials in a secret and reference it here - properties: - passwordKey: - description: Key within secret containing the password, defaults - to DefaultPasswordKey - type: string - secretName: - description: Name of secret in the local namespace - type: string - usernameKey: - description: Key within secret containing the username, defaults - to DefaultUsernameKey - type: string - required: - - secretName - type: object - recordingSelector: - description: Recordings that match this selector belong to this FlightRecorder - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - required: - - recordingSelector - type: object - status: - description: FlightRecorderStatus defines the observed state of FlightRecorder - properties: - events: - description: Listing of events available in the target JVM - items: - description: EventInfo contains metadata for a JFR event type - properties: - category: - description: A hierarchical category used to organize related - event types - items: - type: string - type: array - x-kubernetes-list-type: atomic - description: - description: A description detailing what this event does - type: string - name: - description: Human-readable name for this type of event - type: string - options: - additionalProperties: - description: OptionDescriptor contains metadata for an option - for a particular event type - properties: - defaultValue: - description: The value implicitly used when this option - isn't specified - type: string - description: - description: A description of what this option does - type: string - name: - description: Human-readable name for this option - type: string - required: - - defaultValue - - description - - name - type: object - description: Options that may be used to tune this event. This - map is indexed by the option IDs. - type: object - typeId: - description: The ID used by JFR to uniquely identify this event - type - type: string - required: - - category - - description - - name - - options - - typeId - type: object - type: array - x-kubernetes-list-type: atomic - port: - description: JMX port for target JVM - format: int32 - minimum: 0 - type: integer - target: - description: Reference to the pod/service that this object controls - JFR for - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of - an entire object, this string should contain a valid JSON/Go - field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen - only to have some well-defined way of referencing a part of - an object. TODO: this design is not final and this field is - subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference - is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - templates: - description: Listing of templates available in the target JVM - items: - description: TemplateInfo contains metadata for a JFR template - properties: - description: - description: A description of the template and its performance - impact - type: string - name: - description: The name of the template - type: string - provider: - description: The organization which has provided the template - type: string - type: - description: The type of template, which is either "TARGET" - for built-in templates, or "CUSTOM" for user created templates - enum: - - TARGET - - CUSTOM - type: string - required: - - description - - name - - provider - - type - type: object - type: array - x-kubernetes-list-type: atomic - required: - - events - - port - - target - - templates - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/bundle/manifests/operator.cryostat.io_recordings.yaml b/bundle/manifests/operator.cryostat.io_recordings.yaml deleted file mode 100644 index af55b15d..00000000 --- a/bundle/manifests/operator.cryostat.io_recordings.yaml +++ /dev/null @@ -1,122 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.0 - creationTimestamp: null - name: recordings.operator.cryostat.io -spec: - group: operator.cryostat.io - names: - kind: Recording - listKind: RecordingList - plural: recordings - singular: recording - scope: Namespaced - versions: - - deprecated: true - deprecationWarning: operator.cryostat.io/v1beta1 Recording is deprecated; please - use the Cryostat web application or the Cryostat HTTP API to manage recordings - name: v1beta1 - schema: - openAPIV3Schema: - description: Recording represents a JDK Flight Recording. Create a Recording - object to instruct Cryostat to start a new recording for a Pod. An alternative - to managing recordings with the Cryostat web application. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RecordingSpec defines the desired state of Recording - properties: - archive: - description: Whether this recording should be saved to persistent - storage. If true, the JFR file will be retained until this object - is deleted. If false, the JFR file will be deleted when its corresponding - JVM exits. - type: boolean - duration: - description: The requested total duration of the recording, a zero - value will record indefinitely. The duration format is a combination - of hours (h), minutes (m) and seconds (s). e.g. 30s, 0s, 1h30m - type: string - eventOptions: - description: Name of the event template to use when creating the recording. - Must be prefixed with "template=". e.g. template=Profiling - items: - type: string - type: array - x-kubernetes-list-type: atomic - flightRecorder: - description: Reference to the FlightRecorder object that corresponds - to this Recording. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - name: - description: Name of the recording to be created. - type: string - state: - description: Desired state of the recording. If omitted, RUNNING will - be assumed. - enum: - - RUNNING - - STOPPED - type: string - required: - - archive - - duration - - eventOptions - - flightRecorder - - name - type: object - status: - description: RecordingStatus defines the observed state of Recording - properties: - downloadURL: - description: A URL to download the JFR file for the recording. - type: string - duration: - description: The duration of the recording specified during creation. - type: string - reportURL: - description: A URL to download the autogenerated HTML report for the - recording - type: string - startTime: - description: The date/time when the recording started. - format: date-time - type: string - state: - description: Current state of the recording. - enum: - - CREATED - - RUNNING - - STOPPING - - STOPPED - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 2971d1bd..ed1679fe 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -42,7 +42,7 @@ spec: metadata: type: object spec: - description: CryostatSpec defines the desired state of Cryostat + description: CryostatSpec defines the desired state of Cryostat. properties: authProperties: description: Override default authorization properties for Cryostat @@ -72,16 +72,16 @@ spec: type: boolean eventTemplates: description: List of Flight Recorder Event Templates to preconfigure - in Cryostat + in Cryostat. items: - description: A ConfigMap containing a .jfc template file + description: A ConfigMap containing a .jfc template file. properties: configMapName: - description: Name of config map in the local namespace + description: Name of config map in the local namespace. type: string filename: description: Filename within config map containing the template - file + file. type: string required: - configMapName @@ -90,7 +90,7 @@ spec: type: array jmxCacheOptions: description: Options to customize the JMX target connections cache - for the Cryostat application + for the Cryostat application. properties: targetCacheSize: description: The maximum number of JMX connections to cache. Use @@ -108,7 +108,7 @@ spec: type: object maxWsConnections: description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited) + (minimum 1, default unlimited). format: int32 minimum: 1 type: integer @@ -122,7 +122,7 @@ spec: properties: commandConfig: description: "Specifications for how to expose the Cryostat command - service, which serves the WebSocket command channel \n Deprecated: + service, which serves the WebSocket command channel. \n Deprecated: CommandConfig is no longer used." properties: annotations: @@ -135,7 +135,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -434,7 +434,7 @@ spec: type: object coreConfig: description: Specifications for how to expose the Cryostat service, - which serves the Cryostat application + which serves the Cryostat application. properties: annotations: additionalProperties: @@ -446,7 +446,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -745,7 +745,7 @@ spec: type: object grafanaConfig: description: Specifications for how to expose Cryostat's Grafana - service, which serves the Grafana dashboard + service, which serves the Grafana dashboard. properties: annotations: additionalProperties: @@ -757,7 +757,7 @@ spec: description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is being used) to differentiate - between ingresses/services + between ingresses/services. properties: defaultBackend: description: DefaultBackend is the backend that should @@ -1056,7 +1056,7 @@ spec: type: object type: object reportOptions: - description: Options to configure Cryostat Automated Report Analysis + description: Options to configure Cryostat Automated Report Analysis. properties: replicas: description: The number of report sidecar replica containers to @@ -1101,7 +1101,7 @@ spec: type: integer type: object resources: - description: Resource requirements for the Cryostat deployment + description: Resource requirements for the Cryostat deployment. properties: coreResources: description: Resource requirements for the Cryostat application. @@ -1131,7 +1131,7 @@ spec: type: object type: object dataSourceResources: - description: Resource requirements for the JFR Data Source container + description: Resource requirements for the JFR Data Source container. properties: limits: additionalProperties: @@ -1157,7 +1157,7 @@ spec: type: object type: object grafanaResources: - description: Resource requirements for the Grafana container + description: Resource requirements for the Grafana container. properties: limits: additionalProperties: @@ -1273,7 +1273,7 @@ spec: type: object storageOptions: description: Options to customize the storage for Flight Recordings - and Templates + and Templates. properties: emptyDir: description: Configuration for an EmptyDir to be created by the @@ -1487,14 +1487,14 @@ spec: type: object trustedCertSecrets: description: List of TLS certificates to trust when connecting to - targets + targets. items: properties: certificateKey: - description: Key within secret containing the certificate + description: Key within secret containing the certificate. type: string secretName: - description: Name of secret in the local namespace + description: Name of secret in the local namespace. type: string required: - secretName @@ -1504,14 +1504,14 @@ spec: - minimal type: object status: - description: CryostatStatus defines the observed state of Cryostat + description: CryostatStatus defines the observed state of Cryostat. properties: applicationUrl: - description: Address of the deployed Cryostat web application + description: Address of the deployed Cryostat web application. type: string conditions: description: Conditions of the components managed by the Cryostat - Operator + Operator. items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct @@ -1580,7 +1580,7 @@ spec: type: object type: array grafanaSecret: - description: Name of the Secret containing the generated Grafana credentials + description: Name of the Secret containing the generated Grafana credentials. type: string required: - applicationUrl diff --git a/config/crd/bases/operator.cryostat.io_flightrecorders.yaml b/config/crd/bases/operator.cryostat.io_flightrecorders.yaml deleted file mode 100644 index 82db3dc9..00000000 --- a/config/crd/bases/operator.cryostat.io_flightrecorders.yaml +++ /dev/null @@ -1,247 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.0 - creationTimestamp: null - name: flightrecorders.operator.cryostat.io -spec: - group: operator.cryostat.io - names: - kind: FlightRecorder - listKind: FlightRecorderList - plural: flightrecorders - singular: flightrecorder - scope: Namespaced - versions: - - deprecated: true - deprecationWarning: operator.cryostat.io/v1beta1 FlightRecorder is deprecated; - please use the Cryostat web application or the Cryostat HTTP API to manage recordings - name: v1beta1 - schema: - openAPIV3Schema: - description: FlightRecorder represents a target Pod that is capable of creating - JDK Flight Recordings using Cryostat. The Cryostat operator creates FlightRecorder - objects when it finds compatible Pods. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: FlightRecorderSpec defines the desired state of FlightRecorder - properties: - jmxCredentials: - description: If JMX authentication is enabled for this FlightRecorder's - JVM, specify the credentials in a secret and reference it here - properties: - passwordKey: - description: Key within secret containing the password, defaults - to DefaultPasswordKey - type: string - secretName: - description: Name of secret in the local namespace - type: string - usernameKey: - description: Key within secret containing the username, defaults - to DefaultUsernameKey - type: string - required: - - secretName - type: object - recordingSelector: - description: Recordings that match this selector belong to this FlightRecorder - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - required: - - recordingSelector - type: object - status: - description: FlightRecorderStatus defines the observed state of FlightRecorder - properties: - events: - description: Listing of events available in the target JVM - items: - description: EventInfo contains metadata for a JFR event type - properties: - category: - description: A hierarchical category used to organize related - event types - items: - type: string - type: array - x-kubernetes-list-type: atomic - description: - description: A description detailing what this event does - type: string - name: - description: Human-readable name for this type of event - type: string - options: - additionalProperties: - description: OptionDescriptor contains metadata for an option - for a particular event type - properties: - defaultValue: - description: The value implicitly used when this option - isn't specified - type: string - description: - description: A description of what this option does - type: string - name: - description: Human-readable name for this option - type: string - required: - - defaultValue - - description - - name - type: object - description: Options that may be used to tune this event. This - map is indexed by the option IDs. - type: object - typeId: - description: The ID used by JFR to uniquely identify this event - type - type: string - required: - - category - - description - - name - - options - - typeId - type: object - type: array - x-kubernetes-list-type: atomic - port: - description: JMX port for target JVM - format: int32 - minimum: 0 - type: integer - target: - description: Reference to the pod/service that this object controls - JFR for - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of - an entire object, this string should contain a valid JSON/Go - field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen - only to have some well-defined way of referencing a part of - an object. TODO: this design is not final and this field is - subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference - is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - templates: - description: Listing of templates available in the target JVM - items: - description: TemplateInfo contains metadata for a JFR template - properties: - description: - description: A description of the template and its performance - impact - type: string - name: - description: The name of the template - type: string - provider: - description: The organization which has provided the template - type: string - type: - description: The type of template, which is either "TARGET" - for built-in templates, or "CUSTOM" for user created templates - enum: - - TARGET - - CUSTOM - type: string - required: - - description - - name - - provider - - type - type: object - type: array - x-kubernetes-list-type: atomic - required: - - events - - port - - target - - templates - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd/bases/operator.cryostat.io_recordings.yaml b/config/crd/bases/operator.cryostat.io_recordings.yaml deleted file mode 100644 index 2eda37bd..00000000 --- a/config/crd/bases/operator.cryostat.io_recordings.yaml +++ /dev/null @@ -1,117 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.0 - creationTimestamp: null - name: recordings.operator.cryostat.io -spec: - group: operator.cryostat.io - names: - kind: Recording - listKind: RecordingList - plural: recordings - singular: recording - scope: Namespaced - versions: - - deprecated: true - deprecationWarning: operator.cryostat.io/v1beta1 Recording is deprecated; please - use the Cryostat web application or the Cryostat HTTP API to manage recordings - name: v1beta1 - schema: - openAPIV3Schema: - description: Recording represents a JDK Flight Recording. Create a Recording - object to instruct Cryostat to start a new recording for a Pod. An alternative - to managing recordings with the Cryostat web application. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RecordingSpec defines the desired state of Recording - properties: - archive: - description: Whether this recording should be saved to persistent - storage. If true, the JFR file will be retained until this object - is deleted. If false, the JFR file will be deleted when its corresponding - JVM exits. - type: boolean - duration: - description: The requested total duration of the recording, a zero - value will record indefinitely. The duration format is a combination - of hours (h), minutes (m) and seconds (s). e.g. 30s, 0s, 1h30m - type: string - eventOptions: - description: Name of the event template to use when creating the recording. - Must be prefixed with "template=". e.g. template=Profiling - items: - type: string - type: array - x-kubernetes-list-type: atomic - flightRecorder: - description: Reference to the FlightRecorder object that corresponds - to this Recording. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - name: - description: Name of the recording to be created. - type: string - state: - description: Desired state of the recording. If omitted, RUNNING will - be assumed. - enum: - - RUNNING - - STOPPED - type: string - required: - - archive - - duration - - eventOptions - - flightRecorder - - name - type: object - status: - description: RecordingStatus defines the observed state of Recording - properties: - downloadURL: - description: A URL to download the JFR file for the recording. - type: string - duration: - description: The duration of the recording specified during creation. - type: string - reportURL: - description: A URL to download the autogenerated HTML report for the - recording - type: string - startTime: - description: The date/time when the recording started. - format: date-time - type: string - state: - description: Current state of the recording. - enum: - - CREATED - - RUNNING - - STOPPING - - STOPPED - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 7f6d4f12..4654f635 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,23 +3,17 @@ # It should be run by config/default resources: - bases/operator.cryostat.io_cryostats.yaml -- bases/operator.cryostat.io_recordings.yaml -- bases/operator.cryostat.io_flightrecorders.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_cryostats.yaml -#- patches/webhook_in_recordings.yaml -#- patches/webhook_in_flightrecorders.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_cryostats.yaml -#- patches/cainjection_in_recordings.yaml -#- patches/cainjection_in_flightrecorders.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_flightrecorders.yaml b/config/crd/patches/cainjection_in_flightrecorders.yaml deleted file mode 100644 index 554c83d3..00000000 --- a/config/crd/patches/cainjection_in_flightrecorders.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: flightrecorders.operator.cryostat.io diff --git a/config/crd/patches/cainjection_in_recordings.yaml b/config/crd/patches/cainjection_in_recordings.yaml deleted file mode 100644 index d3dde9e9..00000000 --- a/config/crd/patches/cainjection_in_recordings.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: recordings.operator.cryostat.io diff --git a/config/crd/patches/webhook_in_flightrecorders.yaml b/config/crd/patches/webhook_in_flightrecorders.yaml deleted file mode 100644 index b1ccb102..00000000 --- a/config/crd/patches/webhook_in_flightrecorders.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: flightrecorders.operator.cryostat.io -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert diff --git a/config/crd/patches/webhook_in_recordings.yaml b/config/crd/patches/webhook_in_recordings.yaml deleted file mode 100644 index b71ea3c8..00000000 --- a/config/crd/patches/webhook_in_recordings.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: recordings.operator.cryostat.io -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 95487ac5..4a29ca07 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -14,38 +14,6 @@ metadata: "minimal": false, "trustedCertSecrets": [] } - }, - { - "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "FlightRecorder", - "metadata": { - "name": "example-flightrecorder" - }, - "spec": { - "recordingSelector": { - "matchLabels": { - "operator.cryostat.io/flightrecorder": "example-flightrecorder" - } - } - } - }, - { - "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "Recording", - "metadata": { - "name": "example-recording" - }, - "spec": { - "archive": true, - "duration": "30s", - "eventOptions": [ - "template=ALL" - ], - "flightRecorder": { - "name": "example-flightrecorder" - }, - "name": "example-recording" - } } ] capabilities: Basic Install @@ -68,8 +36,7 @@ metadata: } } } - operators.operatorframework.io/builder: operator-sdk-v1.5.0 - operators.operatorframework.io/internal-objects: '["flightrecorders.operator.cryostat.io","recordings.operator.cryostat.io"]' + operators.operatorframework.io/builder: operator-sdk-v1.22.2 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: github.com/cryostatio/cryostat-operator support: Cryostat Community @@ -136,16 +103,16 @@ spec: path: enableCertManager x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: List of Flight Recorder Event Templates to preconfigure in Cryostat + - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates - - description: Name of config map in the local namespace + - description: Name of config map in the local namespace. displayName: Config Map Name path: eventTemplates[0].configMapName x-descriptors: - urn:alm:descriptor:io.kubernetes:ConfigMap - description: Options to customize the JMX target connections cache for the - Cryostat application + Cryostat application. displayName: JMX Connections Cache Options path: jmxCacheOptions - description: The maximum number of JMX connections to cache. Use `-1` for @@ -161,7 +128,7 @@ spec: x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited) + 1, default unlimited). displayName: Max WebSocket Connections path: maxWsConnections x-descriptors: @@ -177,7 +144,7 @@ spec: displayName: Network Options path: networkOptions - description: "Specifications for how to expose the Cryostat command service, - which serves the WebSocket command channel \n Deprecated: CommandConfig + which serves the WebSocket command channel. \n Deprecated: CommandConfig is no longer used." displayName: Command Config path: networkOptions.commandConfig @@ -188,7 +155,7 @@ spec: path: networkOptions.commandConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.commandConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The @@ -196,7 +163,7 @@ spec: displayName: Labels path: networkOptions.commandConfig.labels - description: Specifications for how to expose the Cryostat service, which - serves the Cryostat application + serves the Cryostat application. displayName: Core Config path: networkOptions.coreConfig - description: Annotations to add to the Ingress or Route during its creation. @@ -204,7 +171,7 @@ spec: path: networkOptions.coreConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.coreConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The @@ -212,7 +179,7 @@ spec: displayName: Labels path: networkOptions.coreConfig.labels - description: Specifications for how to expose Cryostat's Grafana service, - which serves the Grafana dashboard + which serves the Grafana dashboard. displayName: Grafana Config path: networkOptions.grafanaConfig - description: Annotations to add to the Ingress or Route during its creation. @@ -220,14 +187,14 @@ spec: path: networkOptions.grafanaConfig.annotations - description: Configuration for an Ingress object. Currently subpaths are not supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services + being used) to differentiate between ingresses/services. displayName: Ingress Spec path: networkOptions.grafanaConfig.ingressSpec - description: Labels to add to the Ingress or Route during its creation. The label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels - - description: Options to configure Cryostat Automated Report Analysis + - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions - description: The number of report sidecar replica containers to deploy. Each @@ -250,7 +217,7 @@ spec: path: reportOptions.subProcessMaxHeapSize x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Resource requirements for the Cryostat deployment + - description: Resource requirements for the Cryostat deployment. displayName: Resources path: resources - description: Resource requirements for the Cryostat application. If specifying @@ -259,12 +226,12 @@ spec: path: resources.coreResources x-descriptors: - urn:alm:descriptor:com.tectonic.ui:resourceRequirements - - description: Resource requirements for the JFR Data Source container + - description: Resource requirements for the JFR Data Source container. displayName: Data Source Resources path: resources.dataSourceResources x-descriptors: - urn:alm:descriptor:com.tectonic.ui:resourceRequirements - - description: Resource requirements for the Grafana container + - description: Resource requirements for the Grafana container. displayName: Grafana Resources path: resources.grafanaResources x-descriptors: @@ -273,7 +240,7 @@ spec: and Grafana dashboard. displayName: Service Options path: serviceOptions - - description: Options to customize the storage for Flight Recordings and Templates + - description: Options to customize the storage for Flight Recordings and Templates. displayName: Storage Options path: storageOptions - description: Configuration for an EmptyDir to be created by the operator instead @@ -308,148 +275,31 @@ spec: has created the PVC, changes to this field have no effect. displayName: Spec path: storageOptions.pvc.spec - - description: List of TLS certificates to trust when connecting to targets + - description: List of TLS certificates to trust when connecting to targets. displayName: Trusted TLS Certificates path: trustedCertSecrets - - description: Name of secret in the local namespace + - description: Name of secret in the local namespace. displayName: Secret Name path: trustedCertSecrets[0].secretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret statusDescriptors: - - description: Address of the deployed Cryostat web application + - description: Address of the deployed Cryostat web application. displayName: Application URL path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator + - description: Conditions of the components managed by the Cryostat Operator. displayName: Cryostat Conditions path: conditions x-descriptors: - urn:alm:descriptor:io.kubernetes.conditions - - description: Name of the Secret containing the generated Grafana credentials + - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret version: v1beta1 - - description: FlightRecorder represents a target Pod that is capable of creating - JDK Flight Recordings using Cryostat. The Cryostat operator creates FlightRecorder - objects when it finds compatible Pods. - displayName: Flight Recorder - kind: FlightRecorder - name: flightrecorders.operator.cryostat.io - resources: - - kind: Pod - name: "" - version: v1 - - kind: Secret - name: "" - version: v1 - - kind: Service - name: "" - version: v1 - specDescriptors: - - description: If JMX authentication is enabled for this FlightRecorder's JVM, - specify the credentials in a secret and reference it here - displayName: JMXCredentials - path: jmxCredentials - - description: Recordings that match this selector belong to this FlightRecorder - displayName: Recording Selector - path: recordingSelector - statusDescriptors: - - description: Listing of events available in the target JVM - displayName: Events - path: events - - description: JMX port for target JVM - displayName: Port - path: port - x-descriptors: - - urn:alm:descriptor:text - - description: Reference to the pod/service that this object controls JFR for - displayName: Target - path: target - - description: Listing of templates available in the target JVM - displayName: Templates - path: templates - version: v1beta1 - - description: Recording represents a JDK Flight Recording. Create a Recording - object to instruct Cryostat to start a new recording for a Pod. An alternative - to managing recordings with the Cryostat web application. - displayName: Recording - kind: Recording - name: recordings.operator.cryostat.io - resources: - - kind: Pod - name: "" - version: v1 - - kind: Secret - name: "" - version: v1 - - kind: Service - name: "" - version: v1 - specDescriptors: - - description: Whether this recording should be saved to persistent storage. - If true, the JFR file will be retained until this object is deleted. If - false, the JFR file will be deleted when its corresponding JVM exits. - displayName: Archive - path: archive - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:checkbox - - description: The requested total duration of the recording, a zero value will - record indefinitely. The duration format is a combination of hours (h), - minutes (m) and seconds (s). e.g. 30s, 0s, 1h30m - displayName: Duration - path: duration - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:text - - description: Name of the event template to use when creating the recording. - Must be prefixed with "template=". e.g. template=Profiling - displayName: Event Template - path: eventOptions - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:text - - description: Reference to the FlightRecorder object that corresponds to this - Recording. - displayName: Flight Recorder - path: flightRecorder - - description: Name of the recording to be created. - displayName: Name - path: name - - description: Desired state of the recording. If omitted, RUNNING will be assumed. - displayName: State - path: state - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:select:RUNNING - - urn:alm:descriptor:com.tectonic.ui:select:STOPPED - statusDescriptors: - - description: A URL to download the JFR file for the recording. - displayName: Download URL - path: downloadURL - x-descriptors: - - urn:alm:descriptor:org.w3:link - - description: The duration of the recording specified during creation. - displayName: Duration - path: duration - x-descriptors: - - urn:alm:descriptor:text - - description: A URL to download the autogenerated HTML report for the recording - displayName: Report URL - path: reportURL - x-descriptors: - - urn:alm:descriptor:org.w3:link - - description: The date/time when the recording started. - displayName: Start Time - path: startTime - x-descriptors: - - urn:alm:descriptor:text - - description: Current state of the recording. - displayName: State - path: state - x-descriptors: - - urn:alm:descriptor:text - version: v1beta1 description: | Cryostat provides a cloud-based solution for interacting with the JDK Flight Recorder already present in OpenJDK 11+ JVMs. With Cryostat, users can remotely start, stop, retrieve, and even analyze JFR event data, providing the capability to easily take advantage of Flight Recorder's extremely low runtime cost and overhead and the flexibility to monitor applications and analyze recording data without transferring data outside of the cluster the application runs within. ##Prerequisites diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml index 75178dd2..c5729bac 100644 --- a/config/manifests/kustomization.yaml +++ b/config/manifests/kustomization.yaml @@ -4,13 +4,6 @@ resources: - ../samples - ../scorecard -patches: -- path: xdescriptors_patch.yaml - target: - group: operators.coreos.com - version: v1alpha1 - kind: ClusterServiceVersion - name: cryostat-operator.v0.0.0 #patchesJson6902: #- target: # group: apps diff --git a/config/manifests/xdescriptors_patch.yaml b/config/manifests/xdescriptors_patch.yaml deleted file mode 100644 index 8962f07d..00000000 --- a/config/manifests/xdescriptors_patch.yaml +++ /dev/null @@ -1,7 +0,0 @@ -- op: add - path: /spec/customresourcedefinitions/owned/2/specDescriptors/3 - value: - description: Select the FlightRecorder with the name of the target Pod for this Recording. - displayName: Name - path: flightRecorder.name - x-descriptors: [ "urn:alm:descriptor:io.kubernetes:operator.cryostat.io:v1beta1:FlightRecorder" ] diff --git a/config/rbac/flightrecorder_editor_role.yaml b/config/rbac/flightrecorder_editor_role.yaml deleted file mode 100644 index 4ebe414b..00000000 --- a/config/rbac/flightrecorder_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit flightrecorders. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: flightrecorder-editor-role -rules: -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders/status - verbs: - - get diff --git a/config/rbac/flightrecorder_viewer_role.yaml b/config/rbac/flightrecorder_viewer_role.yaml deleted file mode 100644 index 6d622b91..00000000 --- a/config/rbac/flightrecorder_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view flightrecorders. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: flightrecorder-viewer-role -rules: -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders - verbs: - - get - - list - - watch -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders/status - verbs: - - get diff --git a/config/rbac/recording_editor_role.yaml b/config/rbac/recording_editor_role.yaml deleted file mode 100644 index 3547f081..00000000 --- a/config/rbac/recording_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit recordings. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: recording-editor-role -rules: -- apiGroups: - - operator.cryostat.io - resources: - - recordings - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - operator.cryostat.io - resources: - - recordings/status - verbs: - - get diff --git a/config/rbac/recording_viewer_role.yaml b/config/rbac/recording_viewer_role.yaml deleted file mode 100644 index d810062e..00000000 --- a/config/rbac/recording_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view recordings. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: recording-viewer-role -rules: -- apiGroups: - - operator.cryostat.io - resources: - - recordings - verbs: - - get - - list - - watch -- apiGroups: - - operator.cryostat.io - resources: - - recordings/status - verbs: - - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b9b94988..07406112 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -84,31 +84,6 @@ rules: - services/finalizers verbs: - '*' -- apiGroups: - - "" - resources: - - endpoints - - pods - - secrets - - services - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - pods - - secrets - - services - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - "" resources: @@ -160,21 +135,6 @@ rules: - cryostats verbs: - '*' -- apiGroups: - - operator.cryostat.io - resources: - - cryostats - - flightrecorders - verbs: - - '*' -- apiGroups: - - operator.cryostat.io - resources: - - cryostats - - flightrecorders - - recordings - verbs: - - '*' - apiGroups: - operator.cryostat.io resources: @@ -189,40 +149,6 @@ rules: - get - patch - update -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - operator.cryostat.io - resources: - - flightrecorders/status - verbs: - - get - - patch - - update -- apiGroups: - - operator.cryostat.io - resources: - - recordings/finalizers - verbs: - - update -- apiGroups: - - operator.cryostat.io - resources: - - recordings/status - verbs: - - get - - patch - - update - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 610d4fcf..a92a08ce 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,6 +1,4 @@ ## Append samples you want in your CSV to this file as resources ## resources: - operator_v1beta1_cryostat.yaml -- operator_v1beta1_flightrecorder.yaml -- operator_v1beta1_recording.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/operator_v1beta1_flightrecorder.yaml b/config/samples/operator_v1beta1_flightrecorder.yaml deleted file mode 100644 index 0789014c..00000000 --- a/config/samples/operator_v1beta1_flightrecorder.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: operator.cryostat.io/v1beta1 -kind: FlightRecorder -metadata: - name: example-flightrecorder -spec: - recordingSelector: - matchLabels: - "operator.cryostat.io/flightrecorder": "example-flightrecorder" diff --git a/config/samples/operator_v1beta1_recording.yaml b/config/samples/operator_v1beta1_recording.yaml deleted file mode 100644 index 1e4fa5a5..00000000 --- a/config/samples/operator_v1beta1_recording.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: operator.cryostat.io/v1beta1 -kind: Recording -metadata: - name: example-recording -spec: - name: example-recording - flightRecorder: - name: example-flightrecorder - archive: true - duration: 30s - eventOptions: - - "template=ALL" diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 35d7e182..00000000 --- a/docs/api.md +++ /dev/null @@ -1,289 +0,0 @@ -# (Deprecated) Kubernetes API Overview - -This operator provides a Kubernetes API to interact with [Cryostat](https://github.com/cryostatio/cryostat). -This API comes in the form of the `FlightRecorders` and `Recordings` Custom Resource Definitions, and allows you to create, list, delete, and download recordings from a Kubernetes cluster. - -## Retrieving `FlightRecorder` objects -You can use `FlightRecorders` like any other built-in resource on the command line with kubectl/oc. - -```shell -$ kubectl get flightrecorders -NAME AGE -cryostat-sample-6d8dcf5c9f-w5lgq 2m -jmx-listener-55d48f7cfc-8nkln 110s -``` - -`FlightRecorder` objects are created by the operator whenever a new Cryostat-compatible service is detected. -Services that expose a port named `jfr-jmx` are considered compatible. The number of this port is stored in the `status.port` property for use by the operator. Each `FlightRecorder` object maps one-to-one with a Kubernetes service. This service is stored in the `status.target` property of the `FlightRecorder` object. When the operator learns of a new `FlightRecorder` object, it queries Cryostat for a list of all available JFR events for the JVM behind the `FlightRecorder's` service. The details of these event types are stored in the `status.events` property of the `FlightRecorder`. The `spec.recordingSelector` property provides an association of `Recordings` (outlined below) with this `FlightRecorder` object. The operator also queries Cryostat for a list of known Recording Templates provided by the JVM, and any built-in or user-specified templates registered with Cryostat. These are listed in `status.templates` property. - -```shell -$ kubectl get flightrecorder -o yaml jmx-listener-55d48f7cfc-8nkln -``` -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: FlightRecorder -metadata: - labels: - app: jmx-listener-55d48f7cfc-8nkln - name: jmx-listener-55d48f7cfc-8nkln - namespace: cryostat-operator-system - ownerReferences: - - apiVersion: v1 - kind: Pod - name: jmx-listener-55d48f7cfc-8nkln -spec: - recordingSelector: - matchLabels: - operator.cryostat.io/flightrecorder: jmx-listener-55d48f7cfc-8nkln -status: - events: - - category: - - Java Application - description: Writing data to a socket - name: Socket Write - options: - enabled: - defaultValue: "false" - description: Record event - name: Enabled - stackTrace: - defaultValue: "false" - description: Record stack traces - name: Stack Trace - threshold: - defaultValue: 0ns[ns] - description: Record event with duration above or equal to threshold - name: Threshold - typeId: jdk.SocketWrite - - category: - - Java Application - description: Reading data from a socket - name: Socket Read - options: - enabled: - defaultValue: "false" - description: Record event - name: Enabled - stackTrace: - defaultValue: "false" - description: Record stack traces - name: Stack Trace - threshold: - defaultValue: 0ns[ns] - description: Record event with duration above or equal to threshold - name: Threshold - typeId: jdk.SocketRead - port: 9093 - target: - kind: Pod - name: jmx-listener-55d48f7cfc-8nkln - namespace: cryostat-operator-system - templates: - - description: Low overhead configuration safe for continuous use in production environments, typically less than 1 % overhead. - name: Continuous - provider: Oracle - type: TARGET - - description: Low overhead configuration for profiling, typically around 2 % overhead. - name: Profiling - provider: Oracle - type: TARGET - - description: Enable all available events in the target JVM, with default option values. This will be very expensive and is intended primarily for testing Cryostat's own capabilities. - name: ALL - provider: Cryostat - type: TARGET -``` -(Some fields are removed or abbreviated for readability) - -### Configuring JMX Authentication - -If the target Pod for a `FlightRecorder` object is using password JMX authentication, the `FlightRecorder` must be configured with these credentials in order for Cryostat to connect to the Pod. The `spec.jmxCredentials` property tells the operator where to find the JMX authentication credentials for the target of the `FlightRecorder`. The `secretName` property must refer to the name of a Secret within the same namespace as the `FlightRecorder`. The `usernameKey` and `passwordKey` are the names of the keys used to index the username and password within the named Secret. If the `usernameKey` or `passwordKey` properties are omitted, the operator will use the default key names of `username` and `password`. -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: FlightRecorder -metadata: - labels: - app: jmx-listener-55d48f7cfc-8nkln - name: jmx-listener-55d48f7cfc-8nkln - namespace: cryostat-operator-system - ownerReferences: - - apiVersion: v1 - kind: Pod - name: jmx-listener-55d48f7cfc-8nkln -spec: - jmxCredentials: - secretName: my-jmx-auth-secret - usernameKey: my-user-key - passwordKey: my-pass-key -``` - -## Creating a new Flight Recording - -To start a new recording, you will need to create a new `Recording` custom resource. The `Recording` must include the following: - -1. `name`: a string uniquely identifying the recording within that service. -2. `eventOptions`: an array of string options passed to Cryostat. Templates can be specified with the option `"template="`, such as `"template=Profiling"` for the Profiling template. -3. `duration`: length of the requested recording as a [duration string](https://golang.org/pkg/time/#ParseDuration). -4. `archive`: whether to save the completed recording to persistent storage. -5. `flightRecorder`: a [`LocalObjectReference`](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core) pointing to the `FlightRecorder` that should perform the recording. - -The following example can serve as a template when creating your own `Recording` object: -```shell -$ cat my-recording.yaml -``` -```yaml -apiVersion: rhjmc.redhat.com/v1beta1 -kind: Recording -metadata: - name: my-recording -spec: - name: my-recording - eventOptions: - - "jdk.SocketRead:enabled=true" - - "jdk.SocketWrite:enabled=true" - duration: 30s - archive: true - flightRecorder: - name: jmx-listener-55d48f7cfc-8nkln -``` -```shell -$ kubectl create -f my-recording.yaml -``` - -Once the operator has processed the new `Recording`, it will communicate with Cryostat via the referenced `FlightRecorder` to remotely create the JFR recording. Once this occurs, details of the recording are populated in the `status` of the `Recording` object. The `status.duration` property corresponds to the duration the recording was created with, `status.startTime` is when the recording actually started in the target JVM, and `status.state` is the current state of the recording from the following: -* `CREATED`: the recording has been accepted, but has not started yet. -* `RUNNING`: the recording has started and is currently running. -* `STOPPING`: the recording is in the process of finishing. -* `STOPPED`: the recording has completed and the JFR file is fully written. - -```shell -$ kubectl get -o yaml recording/my-recording -``` -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: Recording -metadata: - finalizers: - - operator.cryostat.io/recording.finalizer - labels: - operator.cryostat.io/flightrecorder: jmx-listener-55d48f7cfc-8nkln - name: my-recording - namespace: cryostat-operator-system -spec: - archive: true - duration: 30s - eventOptions: - - jdk.SocketRead:enabled=true - - jdk.SocketWrite:enabled=true - flightRecorder: - name: jmx-listener-55d48f7cfc-8nkln - name: my-recording -status: - downloadURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10.217.0.29:9093%2Fjmxrmi/recordings/my-recording - duration: 30s - reportURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10.217.0.29:9093%2Fjmxrmi/reports/my-recording - startTime: "2021-04-29T22:03:28Z" - state: RUNNING -``` - -### Creating a continuous Flight Recording - -You may not necessarily want your recording to be a fixed duration, in this case you can specify that you want your `Recording` to be continuous. This is done by setting the `spec.duration` to a zero-value. - -```shell -$ cat my-cont-recording.yaml -``` -```yaml -apiVersion: rhjmc.redhat.com/v1beta1 -kind: Recording -metadata: - name: cont-recording -spec: - name: cont-recording - eventOptions: - - "jdk.SocketRead:enabled=true" - - "jdk.SocketWrite:enabled=true" - duration: 0s - archive: true - flightRecorder: - name: jmx-listener-55d48f7cfc-8nkln -``` -```shell -$ kubectl create -f my-cont-recording.yaml -``` - -In order to stop this recording, you'll need to set `spec.state` to `"STOPPED"`, like the following: -```shell -$ kubectl edit recording/cont-recording -``` -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: Recording -metadata: - finalizers: - - operator.cryostat.io/recording.finalizer - labels: - operator.cryostat.io/flightrecorder: jmx-listener-55d48f7cfc-8nkln - name: cont-recording - namespace: cryostat-operator-system -spec: - archive: true - duration: 0s - eventOptions: - - jdk.SocketRead:enabled=true - - jdk.SocketWrite:enabled=true - flightRecorder: - name: jmx-listener-55d48f7cfc-8nkln - name: cont-recording - state: STOPPED -status: - downloadURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10.217.0.29:9093%2Fjmxrmi/recordings/cont-recording - duration: 0s - reportURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10.217.0.29:9093%2Fjmxrmi/reports/cont-recording - startTime: "2021-04-29T22:12:59Z" - state: RUNNING -``` - -## Downloading a Flight Recording - -When Cryostat starts the recording, URLs to the JFR file and automated analysis HTML report are added to `status.downloadURL` and `status.reportURL`, respectively. If `spec.archive` is `true`, the operator archives the recording once completed. The operator then replaces the download and report URLs with persisted versions that do not depend on the lifecycle of the target JVM. - -The JFR file and HTML report can be downloaded from the URLs contained in `downloadURL` and `reportURL` using cURL, or similar tools. - -```shell -$ kubectl get -o yaml recording/my-recording -``` -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: Recording -metadata: - finalizers: - - operator.cryostat.io/recording.finalizer - labels: - operator.cryostat.io/flightrecorder: jmx-listener-55d48f7cfc-8nkln - name: my-recording - namespace: cryostat-operator-system -spec: - archive: true - duration: 30s - eventOptions: - - jdk.SocketRead:enabled=true - - jdk.SocketWrite:enabled=true - flightRecorder: - name: jmx-listener-55d48f7cfc-8nkln - name: my-recording -status: - downloadURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/recordings/10-217-0-29_my-recording_20210429T220400Z.jfr - duration: 30s - reportURL: https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/reports/10-217-0-29_my-recording_20210429T220400Z.jfr - startTime: "2021-04-29T22:03:28Z" - state: STOPPED -``` - -If running on OpenShift, you will need to pass your bearer token with the `curl` request. (You may also need -k if your test cluster uses a self-signed certificate) -```shell -$ curl -k -H "Authorization: Bearer $(oc whoami -t | base64)" \ -https://cryostat-sample-cryostat-operator-system.apps-crc.testing:443/api/v1/recordings/10-217-0-29_my-recording_20210429T220400Z.jfr \ -my-recording.jfr -``` - -You can then open and analyze the recording with [JDK Mission Control](https://github.com/openjdk/jmc/) on your local machine. diff --git a/internal/controllers/client/command_types.go b/internal/controllers/client/command_types.go deleted file mode 100644 index e5eea18d..00000000 --- a/internal/controllers/client/command_types.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package client - -import ( - "fmt" -) - -// RecordingDescriptor contains various metadata for a particular -// flight recording retrieved from the JVM -type RecordingDescriptor struct { - // An identifier used by the JVM to uniquely identify a recording - ID int64 `json:"id"` - // Name of the recording specified during creation - Name string `json:"name"` - // State of the recording within its lifecycle - State string `json:"state"` - // Time when the recording first started, in milliseconds since Unix epoch - StartTime int64 `json:"startTime"` - // How long the recording was configured to run for, in milliseconds - Duration int64 `json:"duration"` - // Whether the recording was configured to record indefinitely - Continuous bool `json:"continuous"` - // Whether this recording was dumped to disk in the host containing the JVM - ToDisk bool `json:"toDisk"` - // The maximum configured size of the recording file - MaxSize int64 `json:"maxSize"` - // The maximum configured age of recorded events - MaxAge int64 `json:"maxAge"` - // URL to download the raw flight recording file - DownloadURL string `json:"downloadUrl"` - // URL to the automated analysis report for this recording - ReportURL string `json:"reportUrl"` -} - -// SavedRecording represents a recording file that has been archived in -// persistent storage by Container JFR -type SavedRecording struct { - Name string `json:"name"` - DownloadURL string `json:"downloadUrl"` - ReportURL string `json:"reportUrl"` -} - -// TargetAddress contains an address that Container JFR can use to connect -// to a particular JVM -type TargetAddress struct { - Host string - Port int32 -} - -func (target TargetAddress) String() string { - return fmt.Sprintf("%s:%d", target.Host, target.Port) -} diff --git a/internal/controllers/client/cryostat_httpclient.go b/internal/controllers/client/cryostat_httpclient.go deleted file mode 100644 index e05336cd..00000000 --- a/internal/controllers/client/cryostat_httpclient.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package client - -import ( - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/go-logr/logr" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -var log = logf.Log.WithName("cryostat_client") - -const ioTimeout = 30 * time.Second - -// Config stores configuration options to connect to Cryostat's -// web server -type Config struct { - // URL to Cryostat's web server - ServerURL *url.URL - // Bearer token to authenticate with Cryostat - AccessToken *string - // Certificate of CA to trust, in PEM format - CACertificate []byte - // JMX authentication credentials - JMXCredentials *JMXAuthCredentials -} - -// JMXAuthCredentials holds the JMX authentication credentials to send along with requests -type JMXAuthCredentials struct { - // JMX authentication username - Username string - // JMX authentication password - Password string -} - -// CryostatClient contains methods for interacting with Cryostats -// REST API -type CryostatClient interface { - ListRecordings(target *TargetAddress) ([]RecordingDescriptor, error) - DumpRecording(target *TargetAddress, name string, seconds int, events []string) error - StartRecording(target *TargetAddress, name string, events []string) error - StopRecording(target *TargetAddress, name string) error - DeleteRecording(target *TargetAddress, name string) error - SaveRecording(target *TargetAddress, name string) (*string, error) - ListSavedRecordings() ([]SavedRecording, error) - DeleteSavedRecording(jfrFile string) error - ListEventTypes(target *TargetAddress) ([]operatorv1beta1.EventInfo, error) - ListTemplates(target *TargetAddress) ([]operatorv1beta1.TemplateInfo, error) -} - -type httpClient struct { - config *Config - client *http.Client -} - -type apiPath struct { - resource string - target *TargetAddress - name *string -} - -const ( - resRecordings = "recordings" - resEvents = "events" - resTemplates = "templates" - attrRecordingName = "recordingName" - attrEvents = "events" - attrDuration = "duration" - cmdStop = "stop" - cmdSave = "save" -) - -// NewHTTPClient creates a client to communicate with Cryostat over HTTP(S) -func NewHTTPClient(config *Config) (CryostatClient, error) { - configCopy := *config - if config.ServerURL == nil { - return nil, errors.New("ServerURL in config must not be nil") - } - if config.AccessToken == nil { - return nil, errors.New("AccessToken in config must not be nil") - } - - // Create CertPool for CA certificate - var rootCAPool *x509.CertPool - if config.CACertificate != nil { - rootCAPool = x509.NewCertPool() - ok := rootCAPool.AppendCertsFromPEM(config.CACertificate) - if !ok { - return nil, errors.New("Failed to parse CA certificate") - } - } - - // Use settings from default Transport with modified TLS config - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = &tls.Config{ - RootCAs: rootCAPool, - } - client := &http.Client{ - Transport: transport, - Timeout: ioTimeout, - } - log.Info("creating new Cryostat client", "server", config.ServerURL) - return &httpClient{ - config: &configCopy, - client: client, - }, nil -} - -// ListRecordings returns a list of its in-memory Flight Recordings -func (c *httpClient) ListRecordings(target *TargetAddress) ([]RecordingDescriptor, error) { - path := &apiPath{ - resource: resRecordings, - target: target, - } - result := []RecordingDescriptor{} - err := c.httpGet(path, &result) - return result, err -} - -// DumpRecording instructs Cryostat to create a new recording of fixed duration -func (c *httpClient) DumpRecording(target *TargetAddress, name string, seconds int, events []string) error { - return c.postRecording(target, name, seconds, events) -} - -// StartRecording instructs Cryostat to create a new continuous recording -func (c *httpClient) StartRecording(target *TargetAddress, name string, events []string) error { - return c.postRecording(target, name, 0, events) -} - -func (c *httpClient) postRecording(target *TargetAddress, name string, seconds int, events []string) error { - path := &apiPath{ - resource: resRecordings, - target: target, - } - values := url.Values{} - values.Add(attrRecordingName, name) - values.Add(attrEvents, strings.Join(events, ",")) - if seconds > 0 { - values.Add(attrDuration, strconv.Itoa(seconds)) - } - result := RecordingDescriptor{} // TODO use this in reconciler to avoid get call - err := c.httpPostForm(path, values, &result) - return err -} - -// StopRecording instructs Cryostat to stop a recording -func (c *httpClient) StopRecording(target *TargetAddress, name string) error { - path := &apiPath{ - resource: resRecordings, - target: target, - name: &name, - } - return c.httpPatch(path, cmdStop, nil) -} - -// DeleteRecording deletes a recording from Cryostat -func (c *httpClient) DeleteRecording(target *TargetAddress, name string) error { - path := &apiPath{ - resource: resRecordings, - target: target, - name: &name, - } - return c.httpDelete(path, nil) -} - -// SaveRecording copies a flight recording file from local memory to persistent storage -func (c *httpClient) SaveRecording(target *TargetAddress, name string) (*string, error) { - path := &apiPath{ - resource: resRecordings, - target: target, - name: &name, - } - var result string - err := c.httpPatch(path, cmdSave, &result) - return &result, err -} - -// ListSavedRecordings returns a list of recordings contained in persistent storage -func (c *httpClient) ListSavedRecordings() ([]SavedRecording, error) { - path := &apiPath{ - resource: resRecordings, - } - result := []SavedRecording{} - err := c.httpGet(path, &result) - return result, err -} - -// DeleteSavedRecording deletes a recording from the persistent storage managed -// by Cryostat -func (c *httpClient) DeleteSavedRecording(jfrFile string) error { - path := &apiPath{ - resource: resRecordings, - name: &jfrFile, - } - return c.httpDelete(path, nil) -} - -// ListEventTypes returns a list of events available in the target JVM -func (c *httpClient) ListEventTypes(target *TargetAddress) ([]operatorv1beta1.EventInfo, error) { - path := &apiPath{ - resource: resEvents, - target: target, - } - result := []operatorv1beta1.EventInfo{} - err := c.httpGet(path, &result) - return result, err -} - -// ListTemplates returns a list of templates available in the target JVM -func (c *httpClient) ListTemplates(target *TargetAddress) ([]operatorv1beta1.TemplateInfo, error) { - path := &apiPath{ - resource: resTemplates, - target: target, - } - result := []operatorv1beta1.TemplateInfo{} - err := c.httpGet(path, &result) - return result, err -} - -func (c *httpClient) httpGet(path *apiPath, result interface{}) error { - return c.sendRequest(http.MethodGet, path, nil, nil, result) -} - -func (c *httpClient) httpPatch(path *apiPath, body string, result interface{}) error { - contentType := "text/plain" - return c.sendRequest(http.MethodPatch, path, strings.NewReader(body), - &contentType, result) -} - -func (c *httpClient) httpPostForm(path *apiPath, formData url.Values, result interface{}) error { - contentType := "application/x-www-form-urlencoded" - return c.sendRequest(http.MethodPost, path, strings.NewReader(formData.Encode()), - &contentType, result) -} - -func (c *httpClient) httpDelete(path *apiPath, result interface{}) error { - return c.sendRequest(http.MethodDelete, path, nil, nil, result) -} - -func (c *httpClient) sendRequest(method string, path *apiPath, body io.Reader, contentType *string, - result interface{}) error { - // Resolve API path with server URL - pathURL, err := path.URL() - if err != nil { - return err - } - requestURL := c.config.ServerURL.ResolveReference(pathURL) - httpLogger := log.WithValues("method", method, "url", requestURL) - - // Create request and set authorization header(s) - req, err := http.NewRequest(method, requestURL.String(), body) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+*c.config.AccessToken) - if contentType != nil { - req.Header.Set("Content-Type", *contentType) - } - - // If JMX authentication credentials are present, set the proper header - jmxCreds := c.config.JMXCredentials - if jmxCreds != nil { - jmxBasicAuth := getBasicAuth(jmxCreds) - req.Header.Set("X-JMX-Authorization", "Basic "+jmxBasicAuth) - } - - // Send request to server - httpLogger.Info("sending request") - resp, err := c.client.Do(req) - if err != nil { - httpLogger.Error(err, "request error") - return err - } - defer resp.Body.Close() - - // Convert non-2xx responses to errors - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - // Error response body will be plain text - errMsg, err := ioutil.ReadAll(resp.Body) - if err != nil { - httpLogger.Error(err, "failed to read error message from response body") - return err - } - err = fmt.Errorf("server returned status: %s", resp.Status) - httpLogger.Error(err, "request failed", "message", string(errMsg)) - return err - } - httpLogger.Info("request succeeded") - - // Decode response body stream directly - return decodeResponse(resp.Body, result, httpLogger) -} - -func decodeResponse(body io.Reader, result interface{}, httpLogger logr.Logger) error { - httpDebug := httpLogger.V(1) - if result != nil { - // If result is of type string, expect response to be plain text - resultStr, ok := result.(*string) - if ok { - buf, err := ioutil.ReadAll(body) - if err != nil { - httpLogger.Error(err, "could not parse plain text response") - return err - } - *resultStr = string(buf) - httpDebug.Info("parsed plain text response", "result", *resultStr) - } else { // Otherwise, decode as JSON into struct - err := json.NewDecoder(body).Decode(result) - if err != nil { - httpLogger.Error(err, "could not parse JSON response") - return err - } - httpDebug.Info("parsed JSON response", "result", result) - } - } - return nil -} - -func (p *apiPath) URL() (*url.URL, error) { - // Build path based on what fields are defined in the receiver - var strPath string - if p.target != nil { - if p.name != nil { - strPath = fmt.Sprintf("/api/v1/targets/%s/%s/%s", url.PathEscape(p.target.String()), p.resource, *p.name) - } else { - strPath = fmt.Sprintf("/api/v1/targets/%s/%s", url.PathEscape(p.target.String()), p.resource) - } - } else if p.name != nil { - strPath = fmt.Sprintf("/api/v1/%s/%s", p.resource, *p.name) - } else { - strPath = fmt.Sprintf("/api/v1/%s", p.resource) - } - return url.Parse(strPath) -} - -func getBasicAuth(creds *JMXAuthCredentials) string { - rawCreds := []byte(creds.Username + ":" + creds.Password) - return base64.StdEncoding.EncodeToString(rawCreds) -} diff --git a/internal/controllers/common/common_reconciler.go b/internal/controllers/common/common_reconciler.go deleted file mode 100644 index a38b785c..00000000 --- a/internal/controllers/common/common_reconciler.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package common - -import ( - "context" - b64 "encoding/base64" - "errors" - "fmt" - "net/url" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -var log = logf.Log.WithName("common_reconciler") - -// ReconcilerConfig contains configuration used to customize a Reconciler -// built with NewReconciler -type ReconcilerConfig struct { - // This client, initialized using mgr.Client(), is a split client - // that reads objects from the cache and writes to the apiserver - Client client.Client - // Optional field to specify an alternate ClientFactory used by - // Reconciler to create CryostatClients - ClientFactory CryostatClientFactory - // Optional field to override the default behaviour when interacting - // with the operating system - OS OSUtils -} - -// Reconciler contains helpful methods to communicate with Cryostat -// It is meant to be embedded within other Reconcilers. -type Reconciler interface { - FindCryostat(ctx context.Context, namespace string) (*operatorv1beta1.Cryostat, error) - GetCryostatClient(ctx context.Context, namespace string, jmxAuth *operatorv1beta1.JMXAuthSecret) (cryostatClient.CryostatClient, error) - GetPodTarget(targetPod *corev1.Pod, jmxPort int32) (*cryostatClient.TargetAddress, error) - ReconcilerTLS -} - -type commonReconciler struct { - *ReconcilerConfig - ReconcilerTLS -} - -// blank assignment to verify that commonReconciler implements Reconciler -var _ Reconciler = &commonReconciler{} - -// NewReconciler creates a new Reconciler using the provided configuration -func NewReconciler(config *ReconcilerConfig) Reconciler { - configCopy := *config - if config.ClientFactory == nil { - configCopy.ClientFactory = &defaultClientFactory{} - } - if config.OS == nil { - configCopy.OS = &defaultOSUtils{} - } - return &commonReconciler{ - ReconcilerConfig: &configCopy, - ReconcilerTLS: NewReconcilerTLS(&ReconcilerTLSConfig{ - Client: configCopy.Client, - OSUtils: configCopy.OS, - }), - } -} - -// GetCryostatClient creates a client to communicate with the Cryostat -// instance deployed by this operator in the given namespace -func (r *commonReconciler) GetCryostatClient(ctx context.Context, namespace string, - jmxAuth *operatorv1beta1.JMXAuthSecret) (cryostatClient.CryostatClient, error) { - // Look up Cryostat instance within the given namespace - cryostat, err := r.FindCryostat(ctx, namespace) - if err != nil { - return nil, err - } - // Get CA certificate if TLS is enabled - var caCert []byte - protocol := "http" - if r.IsCertManagerEnabled(cryostat) { - caCert, err = r.GetCryostatCABytes(ctx, cryostat) - if err != nil { - return nil, err - } - protocol = "https" - } - // Get the URL to the Cryostat web service - serverURL, err := r.getServerURL(ctx, cryostat.Namespace, cryostat.Name, protocol) - if err != nil { - return nil, err - } - // Read bearer token from mounted secret - tok, err := r.OS.GetFileContents("/var/run/secrets/kubernetes.io/serviceaccount/token") - if err != nil { - return nil, err - } - strTok := b64.StdEncoding.EncodeToString(tok) - - // Get JMX authentication credentials, if present - var jmxCreds *cryostatClient.JMXAuthCredentials - if jmxAuth != nil { - jmxCreds, err = r.getJMXCredentialsFromSecret(ctx, namespace, jmxAuth) - if err != nil { - return nil, err - } - } - - // Create Cryostat HTTP(S) client - config := &cryostatClient.Config{ - ServerURL: serverURL, - AccessToken: &strTok, - CACertificate: caCert, - JMXCredentials: jmxCreds, - } - cryostatClient, err := r.ClientFactory.CreateClient(config) - if err != nil { - return nil, err - } - return cryostatClient, nil -} - -// GetPodTarget returns a TargetAddress for a particular pod and port number -func (r *commonReconciler) GetPodTarget(targetPod *corev1.Pod, jmxPort int32) (*cryostatClient.TargetAddress, error) { - // Create TargetAddress using pod's IP address and provided port - podIP, err := getPodIP(targetPod) - if err != nil { - return nil, err - } - return &cryostatClient.TargetAddress{ - Host: *podIP, - Port: jmxPort, - }, nil -} - -func (r *commonReconciler) FindCryostat(ctx context.Context, namespace string) (*operatorv1beta1.Cryostat, error) { - // TODO Consider how to find Cryostat object if this operator becomes cluster-scoped - // Look up the Cryostat object for this operator, which will help us find its services - cryostatList := &operatorv1beta1.CryostatList{} - err := r.Client.List(ctx, cryostatList) - if err != nil { - return nil, err - } - if len(cryostatList.Items) == 0 { - return nil, errors.New("No Cryostat objects found") - } else if len(cryostatList.Items) > 1 { - // Does not seem like a proper use-case - log.Info("More than one Cryostat object found in namespace, using only the first one listed", - "namespace", namespace) - } - return &cryostatList.Items[0], nil -} - -func (r *commonReconciler) getServerURL(ctx context.Context, namespace string, svcName string, protocol string) (*url.URL, error) { - // Look up Cryostat service, and build URL to web service - cryostatSvc := &corev1.Service{} - err := r.Client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: svcName}, cryostatSvc) - if err != nil { - return nil, err - } - webServerPort, err := getWebServerPort(cryostatSvc) - if err != nil { - return nil, err - } - return url.Parse(fmt.Sprintf("%s://%s.%s.svc:%d/", protocol, svcName, namespace, webServerPort)) -} - -func (r *commonReconciler) getJMXCredentialsFromSecret(ctx context.Context, namespace string, - jmxSecret *operatorv1beta1.JMXAuthSecret) (*cryostatClient.JMXAuthCredentials, error) { - // Look up referenced secret - secret := &corev1.Secret{} - err := r.Client.Get(ctx, types.NamespacedName{Name: jmxSecret.SecretName, Namespace: namespace}, secret) - if err != nil { - return nil, err - } - - // Get credentials from secret - username, err := getValueFromSecret(secret, jmxSecret.UsernameKey, operatorv1beta1.DefaultUsernameKey) - if err != nil { - return nil, err - } - password, err := getValueFromSecret(secret, jmxSecret.PasswordKey, operatorv1beta1.DefaultPasswordKey) - if err != nil { - return nil, err - } - - return &cryostatClient.JMXAuthCredentials{ - Username: *username, - Password: *password, - }, nil -} - -func getPodIP(pod *corev1.Pod) (*string, error) { - podIP := pod.Status.PodIP - if len(podIP) == 0 { - return nil, fmt.Errorf("PodIP unavailable for %s", pod.Name) - } - return &podIP, nil -} - -func getWebServerPort(svc *corev1.Service) (int32, error) { - for _, port := range svc.Spec.Ports { - if port.Name == "http" { - return port.Port, nil - } - } - return 0, errors.New("Cryostat service had no port named \"http\"") -} - -func getValueFromSecret(secret *corev1.Secret, key *string, defaultKey string) (*string, error) { - // Use the default key if no key was specified - if key == nil { - key = &defaultKey - } - // Return an error if value is missing in secret - rawValue, pres := secret.Data[*key] - if !pres { - return nil, fmt.Errorf("No key \"%s\" found in secret \"%s/%s\"", *key, secret.Namespace, secret.Name) - } - result := string(rawValue) - return &result, nil -} diff --git a/internal/controllers/common/common_utils.go b/internal/controllers/common/common_utils.go index 276b7837..c354a226 100644 --- a/internal/controllers/common/common_utils.go +++ b/internal/controllers/common/common_utils.go @@ -40,13 +40,10 @@ import ( "io/ioutil" "os" - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" ) -// CryostatClientFactory provides a method for creating Cryostat clients -type CryostatClientFactory interface { - CreateClient(config *cryostatClient.Config) (cryostatClient.CryostatClient, error) -} +var log = logf.Log.WithName("common") // OSUtils is an abstraction on functionality that interacts with the operating system type OSUtils interface { @@ -54,12 +51,6 @@ type OSUtils interface { GetFileContents(path string) ([]byte, error) } -type defaultClientFactory struct{} - -func (c *defaultClientFactory) CreateClient(config *cryostatClient.Config) (cryostatClient.CryostatClient, error) { - return cryostatClient.NewHTTPClient(config) -} - type defaultOSUtils struct{} // GetEnv returns the value of the environment variable with the provided name. If no such diff --git a/internal/controllers/endpoints_controller.go b/internal/controllers/endpoints_controller.go deleted file mode 100644 index 911d3293..00000000 --- a/internal/controllers/endpoints_controller.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers - -import ( - "context" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/controllers/common" - resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - ctrl "sigs.k8s.io/controller-runtime" -) - -// EndpointsReconciler reconciles a Endpoints object -type EndpointsReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - common.Reconciler -} - -// +kubebuilder:rbac:namespace=system,groups="",resources=endpoints;services;pods;secrets,verbs=get;list;watch -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=flightrecorders,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=flightrecorders/status,verbs=get;update;patch - -// Reconcile processes an Endpoints and creates FlightRecorders when compatible -func (r *EndpointsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling Endpoints") - - // Fetch the Endpoints instance - ep := &corev1.Endpoints{} - err := r.Client.Get(ctx, request.NamespacedName, ep) - if err != nil { - if kerrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - for _, subset := range ep.Subsets { - // Check if this subset appears to be compatible with Cryostat - jmxPort := getServiceJMXPort(subset) - - if jmxPort != nil { - for _, address := range subset.Addresses { - target := address.TargetRef - if target != nil && target.Kind == "Pod" { - err := r.handlePodAddress(ctx, target, ep, jmxPort, reqLogger) - if err != nil { - return reconcile.Result{}, err - } - } - } - } - } - - reqLogger.Info("Endpoints successfully reconciled", "Namespace", request.Namespace, "Name", request.Name) - return reconcile.Result{}, nil -} - -func (r *EndpointsReconciler) handlePodAddress(ctx context.Context, target *corev1.ObjectReference, - ep *corev1.Endpoints, jmxPort *int32, reqLogger logr.Logger) error { - // Check if this FlightRecorder already exists - found := &operatorv1beta1.FlightRecorder{} - jfrName := target.Name - - err := r.Client.Get(ctx, types.NamespacedName{Name: jfrName, Namespace: target.Namespace}, found) - if err != nil { - if !kerrors.IsNotFound(err) { - return err - } - - // If this Endpoints is for Cryostat itself, fill in the JMX authentication credentials - // that the operator generated - jmxAuth, err := r.getJMXCredentials(ctx, ep) - if err != nil { - return err - } - - reqLogger.Info("Creating a new FlightRecorder", "Namespace", target.Namespace, "Name", jfrName) - err = r.createNewFlightRecorder(ctx, target, jmxPort, jmxAuth) - if err != nil { - return err - } - } - return nil -} - -const defaultJmxPort int32 = 9091 -const jmxServicePortName = "jfr-jmx" - -func getServiceJMXPort(subset corev1.EndpointSubset) *int32 { - var portNum, fallbackPortNum *int32 - for idx, port := range subset.Ports { - if port.Name == jmxServicePortName { - portNum = &subset.Ports[idx].Port - } else if port.Port == defaultJmxPort { - fallbackPortNum = &subset.Ports[idx].Port - } - } - if portNum == nil && fallbackPortNum != nil { - portNum = fallbackPortNum - } - return portNum -} - -func (r *EndpointsReconciler) createNewFlightRecorder(ctx context.Context, target *corev1.ObjectReference, jmxPort *int32, - jmxAuth *operatorv1beta1.JMXAuthSecret) error { - pod := &corev1.Pod{} - err := r.Client.Get(ctx, types.NamespacedName{Name: target.Name, Namespace: target.Namespace}, pod) - if err != nil { - return err - } - - // Define a new FlightRecorder object for this Pod - jfr, err := r.newFlightRecorderForPod(target, pod, *jmxPort, jmxAuth) - if err != nil { - return err - } - - // Set Pod instance as the owner - ownerRef := metav1.OwnerReference{ - APIVersion: pod.APIVersion, - Kind: pod.Kind, - UID: pod.UID, - Name: pod.Name, - } - jfr.SetOwnerReferences([]metav1.OwnerReference{ownerRef}) - - err = r.Client.Create(ctx, jfr) - if err != nil { - return err - } - // Update FlightRecorder Status - err = r.Client.Status().Update(ctx, jfr) - if err != nil { - return err - } - - return nil -} - -// newFlightRecorderForPod returns a FlightRecorder with the same name/namespace as the target -func (r *EndpointsReconciler) newFlightRecorderForPod(target *corev1.ObjectReference, pod *corev1.Pod, - jmxPort int32, jmxAuth *operatorv1beta1.JMXAuthSecret) (*operatorv1beta1.FlightRecorder, error) { - // Inherit "app" label from endpoints - appLabel := pod.Name // Use endpoints name as fallback - if label, pres := pod.Labels["app"]; pres { - appLabel = label - } - labels := map[string]string{ - "app": appLabel, - } - - // Use label selector matching the name of this FlightRecorder - selector := &metav1.LabelSelector{} - selector = metav1.AddLabelToSelector(selector, operatorv1beta1.RecordingLabel, target.Name) - - return &operatorv1beta1.FlightRecorder{ - ObjectMeta: metav1.ObjectMeta{ - Name: target.Name, - Namespace: target.Namespace, - Labels: labels, - }, - Spec: operatorv1beta1.FlightRecorderSpec{ - RecordingSelector: selector, - JMXCredentials: jmxAuth, - }, - Status: operatorv1beta1.FlightRecorderStatus{ - Events: []operatorv1beta1.EventInfo{}, - Templates: []operatorv1beta1.TemplateInfo{}, - Target: target, - Port: jmxPort, - }, - }, nil -} - -func (r *EndpointsReconciler) getJMXCredentials(ctx context.Context, ep *corev1.Endpoints) (*operatorv1beta1.JMXAuthSecret, error) { - // Look up the Cryostat CR in this namespace - cryostat, err := r.FindCryostat(ctx, ep.Namespace) - if err != nil { - return nil, err - } - - // Get service corresponding to this Endpoints - svc := &corev1.Service{} - err = r.Client.Get(ctx, types.NamespacedName{Name: ep.Name, Namespace: ep.Namespace}, svc) - if err != nil { - return nil, err - } - - // Is the service owned by the Cryostat CR - var result *operatorv1beta1.JMXAuthSecret - if metav1.IsControlledBy(svc, cryostat) { - // Look up JMX auth secret created for this Cryostat - secret := &corev1.Secret{} - err := r.Client.Get(ctx, types.NamespacedName{Name: cryostat.Name + resources.JMXSecretNameSuffix, - Namespace: cryostat.Namespace}, secret) - if err != nil { - return nil, err - } - - // Found the JMX auth secret, fill in corresponding values for FlightRecorder - userKey := resources.JMXSecretUserKey - passKey := resources.JMXSecretPassKey - result = &operatorv1beta1.JMXAuthSecret{ - SecretName: secret.Name, - UsernameKey: &userKey, - PasswordKey: &passKey, - } - } - - return result, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *EndpointsReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Endpoints{}). - Complete(r) -} diff --git a/internal/controllers/endpoints_controller_test.go b/internal/controllers/endpoints_controller_test.go deleted file mode 100644 index 565e0388..00000000 --- a/internal/controllers/endpoints_controller_test.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers_test - -import ( - "context" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/controllers" - "github.com/cryostatio/cryostat-operator/internal/test" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var _ = Describe("EndpointsController", func() { - var ( - objs []runtime.Object - client client.Client - controller *controllers.EndpointsReconciler - ) - - JustBeforeEach(func() { - logger := zap.New() - logf.SetLogger(logger) - s := test.NewTestScheme() - - client = fake.NewFakeClientWithScheme(s, objs...) - controller = &controllers.EndpointsReconciler{ - Client: client, - Scheme: s, - Log: logger, - Reconciler: test.NewTestReconcilerNoServer(client), - } - }) - - AfterEach(func() { - objs = nil - }) - - Describe("reconciling a request", func() { - Context("successfully reconcile", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), test.NewTestService(), - test.NewTargetPod(), test.NewTestEndpoints(), - } - }) - It("should create new flightrecorder", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - found := &operatorv1beta1.FlightRecorder{} - err = client.Get(context.Background(), types.NamespacedName{Name: "test-pod", Namespace: "default"}, found) - Expect(err).ToNot(HaveOccurred()) - // compare found to desired spec - expected := test.NewFlightRecorderNoJMXAuth() - Expect(found.TypeMeta).To(Equal(expected.TypeMeta)) - Expect(found.ObjectMeta.Name).To(Equal(expected.ObjectMeta.Name)) - Expect(found.ObjectMeta.Namespace).To(Equal(expected.ObjectMeta.Namespace)) - Expect(found.ObjectMeta.Labels).To(Equal(expected.ObjectMeta.Labels)) - Expect(found.ObjectMeta.OwnerReferences).To(Equal(expected.ObjectMeta.OwnerReferences)) - Expect(found.Spec).To(Equal(expected.Spec)) - }) - }) - Context("successfully reconcile Cryostat", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), test.NewCryostatService(), - test.NewCryostatEndpoints(), test.NewCryostatPod(), - test.NewJMXAuthSecretForCryostat(), - } - }) - It("should create new flightrecorder", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - found := &operatorv1beta1.FlightRecorder{} - err = client.Get(context.Background(), types.NamespacedName{Name: "cryostat-pod", Namespace: "default"}, found) - Expect(err).ToNot(HaveOccurred()) - // compare found to desired spec - expected := test.NewFlightRecorderForCryostat() - - compareFlightRecorders(found, expected) - }) - }) - Context("endpoints does not exist", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), - } - }) - It("should return without error", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - }) - Context("endpoints has no targetRef", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), test.NewTestService(), - test.NewTargetPod(), test.NewTestEndpointsNoTargetRef(), - } - }) - It("should return without error", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - It("should not create flightrecorder", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - controller.Reconcile(context.Background(), req) - recorder := &operatorv1beta1.FlightRecorder{} - err := client.Get(context.Background(), types.NamespacedName{Name: "test-pod", Namespace: "default"}, recorder) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) - }) - }) - Context("endpoints has no ports", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), test.NewTestService(), - test.NewTargetPod(), test.NewTestEndpointsNoPorts(), - } - }) - It("should return without error", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - It("should not create flightrecorder", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - controller.Reconcile(context.Background(), req) - recorder := &operatorv1beta1.FlightRecorder{} - err := client.Get(context.Background(), types.NamespacedName{Name: "test-pod", Namespace: "default"}, recorder) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) - }) - }) - Context("endpoints only has default port", func() { - BeforeEach(func() { - objs = []runtime.Object{ - test.NewCryostat(), test.NewTestService(), - test.NewTargetPod(), test.NewTestEndpointsNoJMXPort(), - } - }) - It("should return without error", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - result, err := controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - It("should create flightrecorder", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-svc", Namespace: "default"}} - controller.Reconcile(context.Background(), req) - recorder := &operatorv1beta1.FlightRecorder{} - err := client.Get(context.Background(), types.NamespacedName{Name: "test-pod", Namespace: "default"}, recorder) - Expect(err).ToNot(HaveOccurred()) - expected := test.NewFlightRecorderNoJMXAuth() - compareFlightRecorders(recorder, expected) - }) - }) - - }) -}) - -func compareFlightRecorders(found *operatorv1beta1.FlightRecorder, expected *operatorv1beta1.FlightRecorder) { - Expect(found.TypeMeta).To(Equal(expected.TypeMeta)) - Expect(found.ObjectMeta.Name).To(Equal(expected.ObjectMeta.Name)) - Expect(found.ObjectMeta.Namespace).To(Equal(expected.ObjectMeta.Namespace)) - Expect(found.ObjectMeta.Labels).To(Equal(expected.ObjectMeta.Labels)) - Expect(found.ObjectMeta.OwnerReferences).To(Equal(expected.ObjectMeta.OwnerReferences)) - Expect(found.Spec).To(Equal(expected.Spec)) -} diff --git a/internal/controllers/flightrecorder_controller.go b/internal/controllers/flightrecorder_controller.go deleted file mode 100644 index ff54f63d..00000000 --- a/internal/controllers/flightrecorder_controller.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "time" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - common "github.com/cryostatio/cryostat-operator/internal/controllers/common" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -// FlightRecorderReconciler reconciles a FlightRecorder object -type FlightRecorderReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - common.Reconciler -} - -// +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;secrets,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:namespace=system,groups=cert-manager.io,resources=issuers;certificates,verbs=create;get;list;update;watch -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats;flightrecorders,verbs=* -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=flightrecorders/status,verbs=get;update;patch - -// Reconcile processes a FlightRecorder CR and retrieves event/template information from Cryostat -func (r *FlightRecorderReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling FlightRecorder") - - // Fetch the FlightRecorder instance - instance := &operatorv1beta1.FlightRecorder{} - err := r.Client.Get(ctx, request.NamespacedName, instance) - if err != nil { - if kerrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - reqLogger.Info("FlightRecorder does not exist") - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - // Check for a valid target reference - targetRef := instance.Status.Target - if targetRef == nil { - // FlightRecorder status must not have been updated yet - return reconcile.Result{RequeueAfter: time.Second}, nil - } - - // Obtain a client configured to communicate with Cryostat - cryostat, err := r.GetCryostatClient(ctx, request.Namespace, instance.Spec.JMXCredentials) - if err != nil { - if err == common.ErrCertNotReady { - reqLogger.Info("Waiting for CA certificate") - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - return reconcile.Result{}, err - } - - // Look up pod corresponding to this FlightRecorder object - targetPod := &corev1.Pod{} - err = r.Client.Get(ctx, types.NamespacedName{Namespace: targetRef.Namespace, Name: targetRef.Name}, targetPod) - if err != nil { - return reconcile.Result{}, err - } - - // Get a TargetAddress for this pod - targetAddr, err := r.GetPodTarget(targetPod, instance.Status.Port) - if err != nil { - return reconcile.Result{}, err - } - - // Retrieve list of available events - reqLogger.Info("Listing event types for pod", "name", targetPod.Name, "namespace", targetPod.Namespace) - events, err := cryostat.ListEventTypes(targetAddr) - if err != nil { - reqLogger.Error(err, "failed to list event types") - return reconcile.Result{}, err - } - - // Update Status with events - instance.Status.Events = events - - // Retrieve list of available templates - reqLogger.Info("Listing templates for pod", "name", targetPod.Name, "namespace", targetPod.Namespace) - templates, err := cryostat.ListTemplates(targetAddr) - if err != nil { - reqLogger.Error(err, "failed to list templates") - return reconcile.Result{}, err - } - - // Update Status with templates - instance.Status.Templates = templates - - err = r.Client.Status().Update(ctx, instance) - if err != nil { - return reconcile.Result{}, err - } - - reqLogger.Info("FlightRecorder successfully updated", "Namespace", instance.Namespace, "Name", instance.Name) - return reconcile.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *FlightRecorderReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&operatorv1beta1.FlightRecorder{}). - Complete(r) -} diff --git a/internal/controllers/flightrecorder_controller_test.go b/internal/controllers/flightrecorder_controller_test.go deleted file mode 100644 index 1c324a4d..00000000 --- a/internal/controllers/flightrecorder_controller_test.go +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers_test - -import ( - "context" - "net/http" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/controllers" - "github.com/cryostatio/cryostat-operator/internal/test" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -type flightRecorderTestInput struct { - controller *controllers.FlightRecorderReconciler - objs []runtime.Object - handlers []http.HandlerFunc - test.TestReconcilerConfig -} - -var _ = Describe("FlightRecorderController", func() { - var t *flightRecorderTestInput - - JustBeforeEach(func() { - logger := zap.New() - logf.SetLogger(logger) - s := test.NewTestScheme() - - t.Client = fake.NewFakeClientWithScheme(s, t.objs...) - t.Server = test.NewServer(t.Client, t.handlers, t.TLS) - t.controller = &controllers.FlightRecorderReconciler{ - Client: t.Client, - Scheme: s, - Log: logger, - Reconciler: test.NewTestReconciler(&t.TestReconcilerConfig), - } - }) - - JustAfterEach(func() { - t.Server.VerifyRequestsReceived(t.handlers) - t.Server.Close() - }) - - BeforeEach(func() { - t = &flightRecorderTestInput{ - objs: []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), test.NewTargetPod(), - test.NewCryostatService(), test.NewJMXAuthSecret(), - }, - TestReconcilerConfig: test.TestReconcilerConfig{ - TLS: true, - }, - } - }) - - AfterEach(func() { - // Reset test inputs - t = nil - }) - - Describe("reconciling a request", func() { - Context("successfully updates FlightRecorder CR", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesHandler(), - test.NewListTemplatesHandler(), - } - }) - It("should update event type list", func() { - t.expectFlightRecorderReconcileSuccess() - }) - }) - Context("after FlightRecorder already reconciled successfully", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesHandler(), - test.NewListTemplatesHandler(), - test.NewListEventTypesHandler(), - test.NewListTemplatesHandler(), - } - }) - It("should be idempotent", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pod", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - obj := &operatorv1beta1.FlightRecorder{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - - // Reconcile same FlightRecorder again - result, err = t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - obj2 := &operatorv1beta1.FlightRecorder{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj2) - Expect(err).ToNot(HaveOccurred()) - Expect(obj2.Status).To(Equal(obj.Status)) - Expect(obj2.Spec).To(Equal(obj.Spec)) - }) - }) - Context("FlightRecorder does not exist", func() { - It("should do nothing", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "does-not-exist", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - }) - Context("FlightRecorder Status not updated yet", func() { - BeforeEach(func() { - otherFr := test.NewFlightRecorder() - otherFr.Status = operatorv1beta1.FlightRecorderStatus{} - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), otherFr, test.NewTargetPod(), test.NewCryostatService(), - test.NewJMXAuthSecret(), - } - }) - It("should requeue", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pod", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{RequeueAfter: time.Second})) - }) - }) - Context("list-event-types command fails", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("list-templates command fails", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesHandler(), - test.NewListTemplatesFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("Cryostat CR is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewFlightRecorder(), test.NewCACert(), test.NewTargetPod(), test.NewCryostatService(), - test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("Cryostat service is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), test.NewTargetPod(), - test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("Target pod is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), test.NewCryostatService(), - test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("Target pod has no IP", func() { - BeforeEach(func() { - otherPod := test.NewTargetPod() - otherPod.Status.PodIP = "" - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), otherPod, test.NewCryostatService(), - test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("successfully updates FlightRecorder CR without JMX auth", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorderNoJMXAuth(), - test.NewTargetPod(), test.NewCryostatService(), - } - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesNoJMXAuthHandler(), - test.NewListTemplatesNoJMXAuthHandler(), - } - }) - It("should update event type list and template list", func() { - t.expectFlightRecorderReconcileSuccess() - }) - }) - Context("incorrect key name for JMX auth secret", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorderBadJMXUserKey(), - test.NewTargetPod(), test.NewCryostatService(), test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("incorrect password key name for JMX auth secret", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorderBadJMXPassKey(), - test.NewTargetPod(), test.NewCryostatService(), test.NewJMXAuthSecret(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("missing JMX auth secret", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), - test.NewTargetPod(), test.NewCryostatService(), - } - }) - It("should requeue with error", func() { - t.expectFlightRecorderReconcileError() - }) - }) - Context("successfully updates FlightRecorder CR with TLS disabled", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{ - test.NewListEventTypesHandler(), - test.NewListTemplatesHandler(), - } - disableTLS := true - t.EnvDisableTLS = &disableTLS - }) - It("should update event type list", func() { - t.expectFlightRecorderReconcileSuccess() - }) - }) - }) -}) - -func (t *flightRecorderTestInput) expectFlightRecorderReconcileSuccess() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pod", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - obj := &operatorv1beta1.FlightRecorder{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - Expect(obj.Status.Events).To(Equal(test.NewEventTypes())) - Expect(obj.Status.Templates).To(Equal(test.NewTemplates())) -} - -func (t *flightRecorderTestInput) expectFlightRecorderReconcileError() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pod", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).To(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) -} diff --git a/internal/controllers/recording_controller.go b/internal/controllers/recording_controller.go deleted file mode 100644 index 7a986269..00000000 --- a/internal/controllers/recording_controller.go +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - - "fmt" - "net/url" - "path" - "time" - - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" - common "github.com/cryostatio/cryostat-operator/internal/controllers/common" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" -) - -// RecordingReconciler reconciles a Recording object -type RecordingReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - common.Reconciler -} - -// Name used for Finalizer that handles Cryostat recording deletion -const recordingFinalizer = "operator.cryostat.io/recording.finalizer" - -// +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;secrets,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:namespace=system,groups=cert-manager.io,resources=issuers;certificates,verbs=create;get;list;update;watch -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=recordings;flightrecorders;cryostats,verbs=* -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=recordings/status,verbs=get;update;patch -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=recordings/finalizers,verbs=update - -// Reconcile processes a Recording and communicates with Cryostat to create and manage -// a corresponding JDK Flight Recording -func (r *RecordingReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling Recording") - - // Fetch the Recording instance - instance := &operatorv1beta1.Recording{} - err := r.Client.Get(ctx, request.NamespacedName, instance) - if err != nil { - if kerrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - reqLogger.Info("Recording does not exist") - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - // Look up FlightRecorder referenced by this Recording - jfr, err := r.getFlightRecorder(ctx, instance) - if err != nil { - return reconcile.Result{}, err - } - if jfr == nil { - // Check if this Recording is being deleted - if instance.GetDeletionTimestamp() != nil && controllerutil.ContainsFinalizer(instance, recordingFinalizer) { - return r.deleteWithoutLiveTarget(ctx, instance) - } - // No matching FlightRecorder, its corresponding Pod might have been deleted - return reconcile.Result{}, nil - } - - // Obtain a client configured to communicate with Cryostat - cryostat, err := r.GetCryostatClient(ctx, request.Namespace, jfr.Spec.JMXCredentials) - if err != nil { - return r.requeueIfNotReady(err) - } - - // Look up pod corresponding to this FlightRecorder object - targetRef := jfr.Status.Target - if targetRef == nil { - // FlightRecorder status must not have been updated yet - return reconcile.Result{RequeueAfter: time.Second}, nil - } - targetPod := &corev1.Pod{} - err = r.Client.Get(ctx, types.NamespacedName{Namespace: targetRef.Namespace, Name: targetRef.Name}, targetPod) - if err != nil { - return reconcile.Result{}, err - } - - // Get TargetAddress for the referenced pod and port number listed in FlightRecorder - targetAddr, err := r.GetPodTarget(targetPod, jfr.Status.Port) - if err != nil { - return reconcile.Result{}, err - } - - // Check if this Recording is being deleted - if instance.GetDeletionTimestamp() != nil { - if controllerutil.ContainsFinalizer(instance, recordingFinalizer) { - return r.deleteWithLiveTarget(ctx, cryostat, instance, targetAddr) - } - // Ready for deletion - return reconcile.Result{}, nil - } - - // Add our finalizer, so we can clean up Cryostat resources upon deletion - if !controllerutil.ContainsFinalizer(instance, recordingFinalizer) { - err := common.AddFinalizer(ctx, r.Client, instance, recordingFinalizer) - if err != nil { - return reconcile.Result{}, err - } - } - - // Tell Cryostat to create the recording if not already done - if instance.Status.State == nil { // Recording hasn't been created yet - if instance.Spec.Duration.Duration == time.Duration(0) { - r.Log.Info("creating new continuous recording", "name", instance.Spec.Name, "eventOptions", instance.Spec.EventOptions) - err = cryostat.StartRecording(targetAddr, instance.Spec.Name, instance.Spec.EventOptions) - } else { - r.Log.Info("creating new recording", "name", instance.Spec.Name, "duration", instance.Spec.Duration, "eventOptions", instance.Spec.EventOptions) - err = cryostat.DumpRecording(targetAddr, instance.Spec.Name, int(instance.Spec.Duration.Seconds()), instance.Spec.EventOptions) - } - if err != nil { - r.Log.Error(err, "failed to create new recording") - return reconcile.Result{}, err - } - } else if shouldStopRecording(instance) { - r.Log.Info("stopping recording", "name", instance.Spec.Name) - err = cryostat.StopRecording(targetAddr, instance.Spec.Name) - if err != nil { - r.Log.Error(err, "failed to stop recording") - return reconcile.Result{}, err - } - } - - // If the recording is found in Cryostat's list, update Recording.Status with the newest info - r.Log.Info("Looking for recordings for pod", "pod", targetPod.Name, "namespace", targetPod.Namespace) - // Updated Download URL, use existing URL as default - downloadURL := instance.Status.DownloadURL - reportURL := instance.Status.ReportURL - descriptor, err := r.findRecordingByName(cryostat, targetAddr, instance.Spec.Name) - if err != nil { - return reconcile.Result{}, err - } - if descriptor != nil { - state, err := validateRecordingState(descriptor.State) - if err != nil { - // TODO Likely an internal error, requeuing may not help. Status.Condition may be useful. - r.Log.Error(err, "unknown recording state observed from Cryostat") - return reconcile.Result{}, err - } - instance.Status.State = state - instance.Status.StartTime = metav1.Unix(0, descriptor.StartTime*int64(time.Millisecond)) - instance.Status.Duration = metav1.Duration{ - Duration: time.Duration(descriptor.Duration) * time.Millisecond, - } - downloadURL = &descriptor.DownloadURL - reportURL = &descriptor.ReportURL - } - - // Archive completed recording if requested and not already done - isStopped := instance.Status.State != nil && *instance.Status.State == operatorv1beta1.RecordingStateStopped - if instance.Spec.Archive && isStopped { - recording, err := r.archiveStoppedRecording(cryostat, instance, targetAddr) - if err != nil { - return reconcile.Result{}, err - } else if recording == nil { - // Unlikely, but log just in case - r.Log.Info("Cannot find JFR URL just saved", "name", instance.Spec.Name) - } else { - r.Log.Info("updating download URL", "name", instance.Spec.Name, "url", &recording.DownloadURL) - downloadURL = &recording.DownloadURL - r.Log.Info("updating report URL", "name", instance.Spec.Name, "url", &recording.ReportURL) - reportURL = &recording.ReportURL - } - } - instance.Status.DownloadURL = downloadURL - instance.Status.ReportURL = reportURL - - // Update Recording status - err = r.Client.Status().Update(ctx, instance) - if err != nil { - return reconcile.Result{}, err - } - - // Requeue if the recording is still in progress - result := reconcile.Result{} - if !isStopped { - // Check progress of recording after 10 seconds - result.RequeueAfter = 10 * time.Second - } - - reqLogger.Info("Recording successfully updated", "Namespace", instance.Namespace, "Name", instance.Name) - return result, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *RecordingReconciler) SetupWithManager(mgr ctrl.Manager) error { - c := ctrl.NewControllerManagedBy(mgr) - c = c.For(&operatorv1beta1.Recording{}) - c = r.watchFlightRecorders(c, mgr.GetClient()) - - return c.Complete(r) -} - -func (r *RecordingReconciler) getFlightRecorder(ctx context.Context, recording *operatorv1beta1.Recording) (*operatorv1beta1.FlightRecorder, error) { - jfrRef := recording.Spec.FlightRecorder - if jfrRef == nil || len(jfrRef.Name) == 0 { - // TODO set Condition for user/log error - r.Log.Info("FlightRecorder reference missing from Recording", "name", recording.Name, - "namespace", recording.Namespace) - return nil, nil - } - - // Apply FlightRecorder label if not already present - err := r.applyFlightRecorderLabel(ctx, recording, jfrRef.Name) - if err != nil { - return nil, err - } - - jfr := &operatorv1beta1.FlightRecorder{} - err = r.Client.Get(ctx, types.NamespacedName{Namespace: recording.Namespace, Name: jfrRef.Name}, jfr) - if err != nil { - if kerrors.IsNotFound(err) { - // TODO set Condition for user, could be legitimate if pod is deleted - r.Log.Info("FlightRecorder referenced from Recording not found", "name", jfrRef.Name, - "namespace", recording.Namespace) - return nil, nil - } - return nil, err - } - return jfr, nil -} - -func (r *RecordingReconciler) findSavedRecording(cryostat cryostatClient.CryostatClient, filename string) (*cryostatClient.SavedRecording, error) { - // Look for our saved recording in list from Cryostat - savedRecordings, err := cryostat.ListSavedRecordings() - if err != nil { - r.Log.Error(err, "failed to list saved flight recordings") - return nil, err - } - for idx, saved := range savedRecordings { - if filename == saved.Name { - return &savedRecordings[idx], nil - } - } - return nil, nil -} - -func (r *RecordingReconciler) archiveStoppedRecording(cryostat cryostatClient.CryostatClient, recording *operatorv1beta1.Recording, - target *cryostatClient.TargetAddress) (*cryostatClient.SavedRecording, error) { - // Check if existing download URL points to an archived recording - jfrFile, err := recordingFilename(*recording.Status.DownloadURL) - if err != nil { - return nil, err - } - - savedRecording, err := r.findSavedRecording(cryostat, *jfrFile) - if err != nil { - return nil, err - } - if savedRecording != nil { - // Use already archived recording - return savedRecording, nil - } - - // Recording hasn't been archived yet, do so now - r.Log.Info("saving recording", "name", recording.Spec.Name) - filename, err := cryostat.SaveRecording(target, recording.Spec.Name) - if err != nil { - r.Log.Error(err, "failed to save recording", "name", recording.Spec.Name) - return nil, err - } - - // Look up full URL for filename returned by SaveRecording - return r.findSavedRecording(cryostat, *filename) -} - -func (r *RecordingReconciler) removeRecording(cryostat cryostatClient.CryostatClient, target *cryostatClient.TargetAddress, - recording *operatorv1beta1.Recording) error { - // Check if recording exists in Cryostat's in-memory list - recName := recording.Spec.Name - found, err := r.findRecordingByName(cryostat, target, recName) - if err != nil { - return err - } - if found != nil { - // Found matching recording, delete it - err = cryostat.DeleteRecording(target, recName) - if err != nil { - return err - } - r.Log.Info("recording successfully deleted", "name", recName) - } - return nil -} - -func (r *RecordingReconciler) removeSavedRecording(cryostat cryostatClient.CryostatClient, - recording *operatorv1beta1.Recording) error { - if recording.Status.DownloadURL != nil { - jfrFile, err := recordingFilename(*recording.Status.DownloadURL) - if err != nil { - return err - } - // Look for this JFR file within Cryostat's list of saved recordings - found, err := r.findSavedRecording(cryostat, *jfrFile) - if err != nil { - return err - } - - if found != nil { - // JFR file exists, so delete it - err = cryostat.DeleteSavedRecording(*jfrFile) - if err != nil { - return err - } - r.Log.Info("saved recording successfully deleted", "file", jfrFile) - } - } - return nil -} - -func (r *RecordingReconciler) applyFlightRecorderLabel(ctx context.Context, recording *operatorv1beta1.Recording, - jfrName string) error { - // Set label if not present or contains the wrong FlightRecorder name - labels := recording.GetLabels() - if labels[operatorv1beta1.RecordingLabel] != jfrName { - // Initialize labels map if recording has no labels - if labels == nil { - labels = map[string]string{} - } - labels[operatorv1beta1.RecordingLabel] = jfrName - recording.SetLabels(labels) - - err := r.Client.Update(ctx, recording) - if err != nil { - return err - } - r.Log.Info("added label for recording", "namespace", recording.Namespace, "name", recording.Name) - } - return nil -} - -func (r *RecordingReconciler) deleteWithoutLiveTarget(ctx context.Context, recording *operatorv1beta1.Recording) (reconcile.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", recording.Namespace, "Request.Name", recording.Name) - reqLogger.Info("no matching FlightRecorder, proceeding with recording deletion") - - // Obtain a client configured to communicate with Cryostat without JMX credentials - cryostat, err := r.GetCryostatClient(ctx, recording.Namespace, nil) - if err != nil { - return r.requeueIfNotReady(err) - } - - // Delete any persisted JFR file for this recording - err = r.removeSavedRecording(cryostat, recording) - if err != nil { - reqLogger.Error(err, "failed to delete saved recording in Cryostat") - return reconcile.Result{}, err - } - - // Allow deletion to proceed, since no FlightRecorder/Pod to clean up - err = common.RemoveFinalizer(ctx, r.Client, recording, recordingFinalizer) - return reconcile.Result{}, err -} - -func (r *RecordingReconciler) deleteWithLiveTarget(ctx context.Context, cryostat cryostatClient.CryostatClient, - recording *operatorv1beta1.Recording, target *cryostatClient.TargetAddress) (reconcile.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", recording.Namespace, "Request.Name", recording.Name) - // Delete any persisted JFR file for this recording - err := r.removeSavedRecording(cryostat, recording) - if err != nil { - reqLogger.Error(err, "failed to delete saved recording in Cryostat") - return reconcile.Result{}, err - } - - // Delete in-memory recording in Cryostat - err = r.removeRecording(cryostat, target, recording) - if err != nil { - reqLogger.Error(err, "failed to delete recording in Cryostat") - return reconcile.Result{}, err - } - - // Remove our finalizer only once our cleanup logic has succeeded - err = common.RemoveFinalizer(ctx, r.Client, recording, recordingFinalizer) - return reconcile.Result{}, err -} - -func (r *RecordingReconciler) watchFlightRecorders(builder *builder.Builder, cl client.Client) *builder.Builder { - ctx := context.Background() - jfrPredicate := predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - return true - }, - CreateFunc: func(e event.CreateEvent) bool { - return true - }, - } - - mapFunc := func(obj client.Object) []reconcile.Request { - // Look up all recordings that reference the changed FlightRecorder - recordings := &operatorv1beta1.RecordingList{} - selector := labels.SelectorFromSet(labels.Set{ - operatorv1beta1.RecordingLabel: obj.GetName(), - }) - err := cl.List(ctx, recordings, &client.ListOptions{ - Namespace: obj.GetNamespace(), - LabelSelector: selector, - }) - if err != nil { - r.Log.Error(err, "Failed to list Recordings", "namespace", obj.GetNamespace(), - "selector", selector.String()) - } - - // Reconcile each recording that was found - requests := make([]reconcile.Request, len(recordings.Items)) - for idx, recording := range recordings.Items { - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: recording.Namespace, - Name: recording.Name, - }, - } - requests[idx] = request - } - return requests - } - - return builder.Watches( - &source.Kind{Type: &operatorv1beta1.FlightRecorder{}}, - handler.EnqueueRequestsFromMapFunc(mapFunc), - ).WithEventFilter( - jfrPredicate, - ) -} - -func (r *RecordingReconciler) findRecordingByName(cryostat cryostatClient.CryostatClient, target *cryostatClient.TargetAddress, - name string) (*cryostatClient.RecordingDescriptor, error) { - // Get an updated list of in-memory flight recordings - descriptors, err := cryostat.ListRecordings(target) - if err != nil { - r.Log.Error(err, "failed to list flight recordings", "name", name) - return nil, err - } - - for idx, recording := range descriptors { - if recording.Name == name { - return &descriptors[idx], nil - } - } - return nil, nil -} - -func validateRecordingState(state string) (*operatorv1beta1.RecordingState, error) { - convState := operatorv1beta1.RecordingState(state) - switch convState { - case operatorv1beta1.RecordingStateCreated, - operatorv1beta1.RecordingStateRunning, - operatorv1beta1.RecordingStateStopping, - operatorv1beta1.RecordingStateStopped: - return &convState, nil - } - return nil, fmt.Errorf("unknown recording state %s", state) -} - -func recordingFilename(recordingURL string) (*string, error) { - // Grab JFR file base name - downloadURL, err := url.Parse(recordingURL) - if err != nil { - return nil, err - } - jfrFile := path.Base(downloadURL.Path) - return &jfrFile, nil -} - -func shouldStopRecording(recording *operatorv1beta1.Recording) bool { - // Need to know user's request, and current state of recording - requested := recording.Spec.State - current := recording.Status.State - if requested == nil || current == nil { - return false - } - - // Should stop if user wants recording stopped and we're not already doing/done so - return *requested == operatorv1beta1.RecordingStateStopped && *current != operatorv1beta1.RecordingStateStopped && - *current != operatorv1beta1.RecordingStateStopping -} - -func (r *RecordingReconciler) requeueIfNotReady(err error) (reconcile.Result, error) { - if err == common.ErrCertNotReady { - r.Log.Info("Waiting for CA certificate") - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - return reconcile.Result{}, err -} diff --git a/internal/controllers/recording_controller_test.go b/internal/controllers/recording_controller_test.go deleted file mode 100644 index 8d47b234..00000000 --- a/internal/controllers/recording_controller_test.go +++ /dev/null @@ -1,629 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package controllers_test - -import ( - "context" - "net/http" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/controllers" - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" - "github.com/cryostatio/cryostat-operator/internal/test" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -type recordingTestInput struct { - controller *controllers.RecordingReconciler - objs []runtime.Object - handlers []http.HandlerFunc - test.TestReconcilerConfig -} - -var _ = Describe("RecordingController", func() { - var t *recordingTestInput - - JustBeforeEach(func() { - logger := zap.New() - logf.SetLogger(logger) - s := test.NewTestScheme() - - t.Client = fake.NewFakeClientWithScheme(s, t.objs...) - t.Server = test.NewServer(t.Client, t.handlers, t.TLS) - t.controller = &controllers.RecordingReconciler{ - Client: t.Client, - Scheme: s, - Log: logger, - Reconciler: test.NewTestReconciler(&t.TestReconcilerConfig), - } - }) - - JustAfterEach(func() { - t.Server.VerifyRequestsReceived(t.handlers) - t.Server.Close() - }) - - BeforeEach(func() { - t = &recordingTestInput{ - objs: []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), - test.NewTargetPod(), test.NewCryostatService(), test.NewJMXAuthSecret(), - }, - TestReconcilerConfig: test.TestReconcilerConfig{ - TLS: true, - }, - } - }) - - AfterEach(func() { - // Reset test inputs - t = nil - }) - - Describe("reconciling a request", func() { - Context("with a new recording", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRecording()) - t.handlers = []http.HandlerFunc{ - test.NewDumpHandler(), - test.NewListHandler(test.NewRecordingDescriptors("RUNNING", 30000)), - } - }) - It("updates status with recording info", func() { - desc := test.NewRecordingDescriptors("RUNNING", 30000)[0] - t.expectRecordingUpdated(&desc) - }) - It("adds finalizer to recording", func() { - t.expectRecordingFinalizerPresent() - }) - It("should requeue after 10 seconds", func() { - t.expectRecordingResult(reconcile.Result{RequeueAfter: 10 * time.Second}) - }) - }) - Context("with a new recording that fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRecording()) - t.handlers = []http.HandlerFunc{ - test.NewDumpFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("with a new continuous recording", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewContinuousRecording()) - t.handlers = []http.HandlerFunc{ - test.NewStartHandler(), - test.NewListHandler(test.NewRecordingDescriptors("RUNNING", 0)), - } - }) - It("updates status with recording info", func() { - desc := test.NewRecordingDescriptors("RUNNING", 0)[0] - t.expectRecordingUpdated(&desc) - }) - It("should requeue after 10 seconds", func() { - t.expectRecordingResult(reconcile.Result{RequeueAfter: 10 * time.Second}) - }) - }) - Context("with a new continuous recording that fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewContinuousRecording()) - t.handlers = []http.HandlerFunc{ - test.NewStartFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("with a running recording", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRunningRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("RUNNING", 30000)), - } - }) - It("should not change status", func() { - t.expectRecordingStatusUnchaged() - }) - It("should requeue after 10 seconds", func() { - t.expectRecordingResult(reconcile.Result{RequeueAfter: 10 * time.Second}) - }) - }) - Context("with a running recording not found in Cryostat", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRunningRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler([]cryostatClient.RecordingDescriptor{}), - } - }) - It("should not change status", func() { - t.expectRecordingStatusUnchaged() - }) - It("should requeue after 10 seconds", func() { - t.expectRecordingResult(reconcile.Result{RequeueAfter: 10 * time.Second}) - }) - }) - Context("when listing recordings fail", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRunningRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListFailHandler(test.NewRecordingDescriptors("RUNNING", 30000)), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("when listing recordings has unexpected state", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRunningRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("DOES-NOT-EXIST", 30000)), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("with a running recording to be stopped", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRecordingToStop()) - t.handlers = []http.HandlerFunc{ - test.NewStopHandler(), - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 0)), - } - }) - It("should stop recording", func() { - desc := test.NewRecordingDescriptors("STOPPED", 0)[0] - t.expectRecordingUpdated(&desc) - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("with a running recording to be stopped that fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRecordingToStop()) - t.handlers = []http.HandlerFunc{ - test.NewStopFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("with a stopped recording to be archived", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewStoppedRecordingToArchive()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewListSavedHandler([]cryostatClient.SavedRecording{}), - test.NewSaveHandler(), - test.NewListSavedHandler(test.NewSavedRecordings()), - } - }) - It("should update download URL", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - - before := &operatorv1beta1.Recording{} - err := t.Client.Get(context.Background(), req.NamespacedName, before) - Expect(err).ToNot(HaveOccurred()) - - _, err = t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - - after := &operatorv1beta1.Recording{} - err = t.Client.Get(context.Background(), req.NamespacedName, after) - Expect(err).ToNot(HaveOccurred()) - - // Should all be the same except for Download URL - saved := test.NewSavedRecordings()[0] - Expect(after.Status.State).To(Equal(before.Status.State)) - Expect(after.Status.Duration).To(Equal(before.Status.Duration)) - Expect(after.Status.StartTime).To(Equal(before.Status.StartTime)) - Expect(after.Status.DownloadURL).ToNot(BeNil()) - Expect(*after.Status.DownloadURL).To(Equal(saved.DownloadURL)) - Expect(after.Status.ReportURL).ToNot(BeNil()) - Expect(*after.Status.ReportURL).To(Equal(saved.ReportURL)) - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("when listing saved recordings fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewStoppedRecordingToArchive()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewListSavedFailHandler(test.NewSavedRecordings()), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("when archiving recording fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewStoppedRecordingToArchive()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewListSavedHandler(test.NewSavedRecordings()), - test.NewSaveFailHandler(), - } - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("with a running recording to be stopped and archived", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewRecordingToStopAndArchive()) - t.handlers = []http.HandlerFunc{ - test.NewStopHandler(), - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewListSavedHandler([]cryostatClient.SavedRecording{}), - test.NewSaveHandler(), - test.NewListSavedHandler(test.NewSavedRecordings()), - } - }) - It("should stop recording", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - _, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - - obj := &operatorv1beta1.Recording{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - - Expect(obj.Status.State).ToNot(BeNil()) - Expect(*obj.Status.State).To(Equal(operatorv1beta1.RecordingStateStopped)) - }) - It("should update download URL", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - _, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - - obj := &operatorv1beta1.Recording{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - - Expect(obj.Status.DownloadURL).ToNot(BeNil()) - Expect(*obj.Status.DownloadURL).To(Equal("http://path/to/saved-test-recording.jfr")) - }) - It("should update report URL", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - _, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - - obj := &operatorv1beta1.Recording{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - - Expect(obj.Status.ReportURL).ToNot(BeNil()) - Expect(*obj.Status.ReportURL).To(Equal("http://path/to/saved-test-recording.html")) - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("with an archived recording", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewArchivedRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewListSavedHandler(test.NewSavedRecordings()), - } - }) - It("should not change status", func() { - t.expectRecordingStatusUnchaged() - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("with a deleted archived recording", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewDeletedArchivedRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListSavedHandler(test.NewSavedRecordings()), - test.NewDeleteSavedHandler(), - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewDeleteHandler(), - } - }) - It("should delete the recording", func() { - t.expectNoRecording() - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("with a deleted recording with missing FlightRecorder", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewTargetPod(), - test.NewCryostatService(), test.NewDeletedArchivedRecording(), - test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{ - test.NewListSavedNoJMXAuthHandler(test.NewSavedRecordings()), - test.NewDeleteSavedNoJMXAuthHandler(), - } - }) - It("should delete the recording", func() { - t.expectNoRecording() - }) - It("should not requeue", func() { - t.expectRecordingResult(reconcile.Result{}) - }) - }) - Context("when deleting the saved recording fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewDeletedArchivedRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListSavedHandler(test.NewSavedRecordings()), - test.NewDeleteSavedFailHandler(), - } - }) - It("should keep the finalizer", func() { - t.expectRecordingFinalizerPresent() - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("when deleting the in-memory recording fails", func() { - BeforeEach(func() { - t.objs = append(t.objs, test.NewDeletedArchivedRecording()) - t.handlers = []http.HandlerFunc{ - test.NewListSavedHandler(test.NewSavedRecordings()), - test.NewDeleteSavedHandler(), - test.NewListHandler(test.NewRecordingDescriptors("STOPPED", 30000)), - test.NewDeleteFailHandler(), - } - }) - It("should keep the finalizer", func() { - t.expectRecordingFinalizerPresent() - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("Recording does not exist", func() { - BeforeEach(func() { - t.handlers = []http.HandlerFunc{} - }) - It("should do nothing", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "does-not-exist", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - }) - Context("FlightRecorder Status not updated yet", func() { - BeforeEach(func() { - otherFr := test.NewFlightRecorder() - otherFr.Status = operatorv1beta1.FlightRecorderStatus{} - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), otherFr, test.NewTargetPod(), - test.NewCryostatService(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should requeue", func() { - t.expectRecordingResult(reconcile.Result{RequeueAfter: time.Second}) - }) - }) - Context("Cryostat CR is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewFlightRecorder(), test.NewCACert(), test.NewTargetPod(), - test.NewCryostatService(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("Cryostat service is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), - test.NewTargetPod(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("FlightRecorder is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewTargetPod(), - test.NewCryostatService(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should not requeue", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - It("should set FlightRecorder label", func() { - obj := t.reconcileRecordingAndGet() - Expect(obj.Labels).To(HaveKeyWithValue(operatorv1beta1.RecordingLabel, "test-pod")) - }) - }) - Context("FlightRecorder is not defined in Recording", func() { - BeforeEach(func() { - recording := test.NewRecording() - recording.Spec.FlightRecorder = nil - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), test.NewTargetPod(), - test.NewCryostatService(), recording, test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should not requeue", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - }) - It("should not set FlightRecorder label", func() { - obj := t.reconcileRecordingAndGet() - Expect(obj.Labels).To(BeEmpty()) - }) - }) - Context("Target pod is missing", func() { - BeforeEach(func() { - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), - test.NewCryostatService(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - Context("Target pod has no IP", func() { - BeforeEach(func() { - otherPod := test.NewTargetPod() - otherPod.Status.PodIP = "" - t.objs = []runtime.Object{ - test.NewCryostat(), test.NewCACert(), test.NewFlightRecorder(), otherPod, - test.NewCryostatService(), test.NewRecording(), test.NewJMXAuthSecret(), - } - t.handlers = []http.HandlerFunc{} - }) - It("should requeue with error", func() { - t.expectRecordingReconcileError() - }) - }) - }) -}) - -func (t *recordingTestInput) expectRecordingUpdated(desc *cryostatClient.RecordingDescriptor) { - obj := t.reconcileRecordingAndGet() - - Expect(obj.Labels).To(HaveKeyWithValue(operatorv1beta1.RecordingLabel, "test-pod")) - - Expect(obj.Status.State).ToNot(BeNil()) - Expect(*obj.Status.State).To(Equal(operatorv1beta1.RecordingState(desc.State))) - // Converted to RFC3339 during serialization (sub-second precision lost) - expectedTime := metav1.Unix(0, desc.StartTime*int64(time.Millisecond)).Rfc3339Copy() - Expect(obj.Status.State).ToNot(BeNil()) - Expect(obj.Status.StartTime.Equal(&expectedTime)).To(BeTrue()) - Expect(obj.Status.Duration).To(Equal(metav1.Duration{ - Duration: time.Duration(desc.Duration) * time.Millisecond, - })) - Expect(obj.Status.DownloadURL).ToNot(BeNil()) - Expect(*obj.Status.DownloadURL).To(Equal(desc.DownloadURL)) - Expect(obj.Status.ReportURL).ToNot(BeNil()) - Expect(*obj.Status.ReportURL).To(Equal(desc.ReportURL)) -} - -func (t *recordingTestInput) expectRecordingStatusUnchaged() { - before := &operatorv1beta1.Recording{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "my-recording", Namespace: "default"}, before) - Expect(err).ToNot(HaveOccurred()) - - after := t.reconcileRecordingAndGet() - Expect(after.Status).To(Equal(before.Status)) -} - -func (t *recordingTestInput) expectRecordingFinalizerPresent() { - obj := t.reconcileRecordingAndGet() - finalizers := obj.GetFinalizers() - Expect(finalizers).To(ContainElement("operator.cryostat.io/recording.finalizer")) -} - -func (t *recordingTestInput) expectNoRecording() { - // Reconcile - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) - - // Check for deleted recording - obj := &operatorv1beta1.Recording{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "my-recording", Namespace: "default"}, obj) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) -} - -func (t *recordingTestInput) expectRecordingReconcileError() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).To(HaveOccurred()) - Expect(result).To(Equal(reconcile.Result{})) -} - -func (t *recordingTestInput) expectRecordingResult(result reconcile.Result) { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - result, err := t.controller.Reconcile(context.Background(), req) - Expect(err).ToNot(HaveOccurred()) - Expect(result).To(Equal(result)) -} - -func (t *recordingTestInput) reconcileRecordingAndGet() *operatorv1beta1.Recording { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "my-recording", Namespace: "default"}} - t.controller.Reconcile(context.Background(), req) - - obj := &operatorv1beta1.Recording{} - err := t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) - return obj -} diff --git a/internal/main.go b/internal/main.go index 64545a57..f52f7bfa 100644 --- a/internal/main.go +++ b/internal/main.go @@ -148,39 +148,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Cryostat") os.Exit(1) } - if err = (&controllers.RecordingReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Recording"), - Scheme: mgr.GetScheme(), - Reconciler: common.NewReconciler(&common.ReconcilerConfig{ - Client: mgr.GetClient(), - }), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Recording") - os.Exit(1) - } - if err = (&controllers.FlightRecorderReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("FlightRecorder"), - Scheme: mgr.GetScheme(), - Reconciler: common.NewReconciler(&common.ReconcilerConfig{ - Client: mgr.GetClient(), - }), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "FlightRecorder") - os.Exit(1) - } - if err = (&controllers.EndpointsReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Endpoints"), - Scheme: mgr.GetScheme(), - Reconciler: common.NewReconciler(&common.ReconcilerConfig{ - Client: mgr.GetClient(), - }), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Endpoints") - os.Exit(1) - } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/internal/test/handlers.go b/internal/test/handlers.go deleted file mode 100644 index 0bbb99ad..00000000 --- a/internal/test/handlers.go +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package test - -import ( - "net/http" - "strconv" - - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" - "github.com/onsi/gomega/ghttp" -) - -func NewDumpHandler() http.HandlerFunc { - return createRecordingHandler(30, true) -} - -func NewDumpFailHandler() http.HandlerFunc { - return createRecordingHandler(30, false) -} - -func NewStartHandler() http.HandlerFunc { - return createRecordingHandler(0, true) -} - -func NewStartFailHandler() http.HandlerFunc { - return createRecordingHandler(0, false) -} - -func createRecordingHandler(duration int64, succeed bool) http.HandlerFunc { - desc := NewRecordingDescriptors("CREATED", duration)[0] - handlers := []http.HandlerFunc{ - ghttp.VerifyRequest(http.MethodPost, "/api/v1/targets/1.2.3.4:8001/recordings"), - ghttp.VerifyContentType("application/x-www-form-urlencoded"), - ghttp.VerifyFormKV("recordingName", "test-recording"), - ghttp.VerifyFormKV("events", "jdk.socketRead:enabled=true,jdk.socketWrite:enabled=true"), - verifyToken(), - verifyJMXAuth(), - } - if duration > 0 { - handlers = append(handlers, ghttp.VerifyFormKV("duration", strconv.Itoa(int(duration)))) - } - if succeed { - handlers = append(handlers, ghttp.RespondWithJSONEncoded(http.StatusOK, desc)) - } else { - handlers = append(handlers, ghttp.RespondWith(http.StatusBadRequest, - "Recording with name \"test-recording\" already exists")) - } - return ghttp.CombineHandlers(handlers...) -} - -func NewStopHandler() http.HandlerFunc { - return stopHandler(true) -} - -func NewStopFailHandler() http.HandlerFunc { - return stopHandler(false) -} - -func stopHandler(succeed bool) http.HandlerFunc { - handlers := []http.HandlerFunc{ - ghttp.VerifyRequest(http.MethodPatch, "/api/v1/targets/1.2.3.4:8001/recordings/test-recording"), - ghttp.VerifyContentType("text/plain"), - ghttp.VerifyBody([]byte("stop")), - verifyToken(), - verifyJMXAuth(), - } - if succeed { - handlers = append(handlers, ghttp.RespondWith(http.StatusOK, nil)) - } else { - handlers = append(handlers, ghttp.RespondWith(http.StatusNotFound, - "Recording with name \"test-recording\" not found")) - } - return ghttp.CombineHandlers(handlers...) -} - -func NewSaveHandler() http.HandlerFunc { - return saveHandler(true) -} - -func NewSaveFailHandler() http.HandlerFunc { - return saveHandler(false) -} - -func saveHandler(succeed bool) http.HandlerFunc { - handlers := []http.HandlerFunc{ - ghttp.VerifyRequest(http.MethodPatch, "/api/v1/targets/1.2.3.4:8001/recordings/test-recording"), - ghttp.VerifyContentType("text/plain"), - ghttp.VerifyBody([]byte("save")), - verifyToken(), - verifyJMXAuth(), - } - if succeed { - handlers = append(handlers, ghttp.RespondWith(http.StatusOK, "saved-test-recording.jfr")) - } else { - handlers = append(handlers, ghttp.RespondWith(http.StatusNotFound, - "Recording with name \"test-recording\" not found")) - } - return ghttp.CombineHandlers(handlers...) -} - -func NewListHandler(descriptors []cryostatClient.RecordingDescriptor) http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/recordings"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWithJSONEncoded(http.StatusOK, descriptors), - ) -} - -func NewListFailHandler(descriptors []cryostatClient.RecordingDescriptor) http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/recordings"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWith(http.StatusInternalServerError, "test message"), - ) -} - -func NewRecordingDescriptors(state string, duration int64) []cryostatClient.RecordingDescriptor { - return []cryostatClient.RecordingDescriptor{ - { - Name: "test-recording", - State: state, - StartTime: 1597090030341, - Duration: duration, - DownloadURL: "http://path/to/test-recording.jfr", - ReportURL: "http://path/to/test-recording.html", - }, - } -} - -func NewListSavedHandler(saved []cryostatClient.SavedRecording) http.HandlerFunc { - return newListSavedHandler(saved, true, true) -} - -func NewListSavedNoJMXAuthHandler(saved []cryostatClient.SavedRecording) http.HandlerFunc { - return newListSavedHandler(saved, false, true) -} - -func NewListSavedFailHandler(saved []cryostatClient.SavedRecording) http.HandlerFunc { - return newListSavedHandler(saved, true, false) -} - -func newListSavedHandler(saved []cryostatClient.SavedRecording, jmxAuth bool, succeed bool) http.HandlerFunc { - handlers := []http.HandlerFunc{ - ghttp.VerifyRequest(http.MethodGet, "/api/v1/recordings"), - verifyToken(), - } - if jmxAuth { - handlers = append(handlers, verifyJMXAuth()) - } - if succeed { - handlers = append(handlers, ghttp.RespondWithJSONEncoded(http.StatusOK, saved)) - } else { - handlers = append(handlers, ghttp.RespondWith(http.StatusNotImplemented, "Archive path /bad/dir does not exist")) - } - return ghttp.CombineHandlers(handlers...) -} - -func NewSavedRecordings() []cryostatClient.SavedRecording { - return []cryostatClient.SavedRecording{ - { - Name: "saved-test-recording.jfr", - DownloadURL: "http://path/to/saved-test-recording.jfr", - ReportURL: "http://path/to/saved-test-recording.html", - }, - } -} - -func NewDeleteHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodDelete, "/api/v1/targets/1.2.3.4:8001/recordings/test-recording"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWith(http.StatusOK, nil), - ) -} - -func NewDeleteFailHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodDelete, "/api/v1/targets/1.2.3.4:8001/recordings/test-recording"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWith(http.StatusNotFound, - "No recording with name \"test-recording\" found"), - ) -} - -func NewDeleteSavedHandler() http.HandlerFunc { - return newDeleteSavedHandler(true, true) -} - -func NewDeleteSavedNoJMXAuthHandler() http.HandlerFunc { - return newDeleteSavedHandler(false, true) -} - -func NewDeleteSavedFailHandler() http.HandlerFunc { - return newDeleteSavedHandler(true, false) -} - -func newDeleteSavedHandler(jmxAuth bool, succeed bool) http.HandlerFunc { - handlers := []http.HandlerFunc{ - ghttp.VerifyRequest(http.MethodDelete, "/api/v1/recordings/saved-test-recording.jfr"), - verifyToken(), - } - if jmxAuth { - handlers = append(handlers, verifyJMXAuth()) - } - if succeed { - handlers = append(handlers, ghttp.RespondWith(http.StatusOK, nil)) - } else { - handlers = append(handlers, ghttp.RespondWith(http.StatusNotFound, "saved-test-recording.jfr")) - } - return ghttp.CombineHandlers(handlers...) -} - -func NewListEventTypesHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/events"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWithJSONEncoded(http.StatusOK, NewEventTypes()), - ) -} - -func NewListEventTypesNoJMXAuthHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/events"), - verifyToken(), - ghttp.RespondWithJSONEncoded(http.StatusOK, NewEventTypes()), - ) -} - -func NewListEventTypesFailHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/events"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWith(http.StatusUnauthorized, nil), - ) -} - -func NewEventTypes() []operatorv1beta1.EventInfo { - return []operatorv1beta1.EventInfo{ - { - TypeID: "jdk.socketRead", - Name: "Socket Read", - Description: "Reading data from a socket", - Category: []string{"Java Application"}, - Options: map[string]operatorv1beta1.OptionDescriptor{ - "enabled": { - Name: "Enabled", - Description: "Record event", - DefaultValue: "false", - }, - "stackTrace": { - Name: "Stack Trace", - Description: "Record stack traces", - DefaultValue: "false", - }, - "threshold": { - Name: "Threshold", - Description: "Record event with duration above or equal to threshold", - DefaultValue: "0ns[ns]", - }, - }, - }, - } -} - -func NewListTemplatesHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/templates"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWithJSONEncoded(http.StatusOK, NewTemplates()), - ) -} - -func NewListTemplatesNoJMXAuthHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/templates"), - verifyToken(), - ghttp.RespondWithJSONEncoded(http.StatusOK, NewTemplates()), - ) -} - -func NewListTemplatesFailHandler() http.HandlerFunc { - return ghttp.CombineHandlers( - ghttp.VerifyRequest(http.MethodGet, "/api/v1/targets/1.2.3.4:8001/templates"), - verifyToken(), - verifyJMXAuth(), - ghttp.RespondWith(http.StatusUnauthorized, nil), - ) -} - -func NewTemplates() []operatorv1beta1.TemplateInfo { - return []operatorv1beta1.TemplateInfo{ - { - Name: "Profiling", - Description: "Low overhead configuration for profiling, typically around 2 % overhead.", - Provider: "Oracle", - Type: "TARGET", - }, - } -} - -func verifyToken() http.HandlerFunc { - return ghttp.VerifyHeaderKV("Authorization", "Bearer bXlUb2tlbg==") -} - -func verifyJMXAuth() http.HandlerFunc { - return ghttp.VerifyHeaderKV("X-JMX-Authorization", "Basic aGVsbG86d29ybGQ=") -} diff --git a/internal/test/reconciler.go b/internal/test/reconciler.go index cfb2c292..5788c81d 100644 --- a/internal/test/reconciler.go +++ b/internal/test/reconciler.go @@ -37,19 +37,16 @@ package test import ( - "net/url" "strconv" "sigs.k8s.io/controller-runtime/pkg/client" - cryostatClient "github.com/cryostatio/cryostat-operator/internal/controllers/client" "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/onsi/gomega" ) // TestReconcilerConfig groups parameters used to create a test Reconciler type TestReconcilerConfig struct { - Server *CryostatServer Client client.Client TLS bool EnvDisableTLS *bool @@ -59,27 +56,6 @@ type TestReconcilerConfig struct { EnvReportsImageTag *string } -// NewTestReconciler returns a common.Reconciler for use by unit tests -func NewTestReconciler(config *TestReconcilerConfig) common.Reconciler { - return common.NewReconciler(&common.ReconcilerConfig{ - Client: config.Client, - ClientFactory: &testClientFactory{config}, - OS: newTestOSUtils(config), - }) -} - -type testClientFactory struct { - *TestReconcilerConfig -} - -func NewTestReconcilerNoServer(client client.Client) common.Reconciler { - return common.NewReconciler(&common.ReconcilerConfig{ - Client: client, - ClientFactory: &testClientFactory{}, - OS: &testOSUtils{}, - }) -} - func NewTestReconcilerTLS(config *TestReconcilerConfig) common.ReconcilerTLS { return common.NewReconcilerTLS(&common.ReconcilerTLSConfig{ Client: config.Client, @@ -87,22 +63,6 @@ func NewTestReconcilerTLS(config *TestReconcilerConfig) common.ReconcilerTLS { }) } -func (c *testClientFactory) CreateClient(config *cryostatClient.Config) (cryostatClient.CryostatClient, error) { - protocol := "https" - if !c.TLS { - protocol = "http" - } - // Verify the provided server URL before substituting it - gomega.Expect(config.ServerURL.String()).To(gomega.Equal(protocol + "://cryostat.default.svc:8181/")) - - // Replace server URL with one to httptest server - url, err := url.Parse(c.Server.impl.URL()) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - config.ServerURL = url - - return cryostatClient.NewHTTPClient(config) -} - type testOSUtils struct { envs map[string]string } diff --git a/internal/test/resources.go b/internal/test/resources.go index fa57ed76..3475c523 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -37,8 +37,6 @@ package test import ( - "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" @@ -421,315 +419,6 @@ func NewCryostatWithReportSubprocessHeapSpec() *operatorv1beta1.Cryostat { return cr } -func NewFlightRecorder() *operatorv1beta1.FlightRecorder { - return newFlightRecorder(&operatorv1beta1.JMXAuthSecret{ - SecretName: "test-jmx-auth", - }) -} - -func NewFlightRecorderNoJMXAuth() *operatorv1beta1.FlightRecorder { - return newFlightRecorder(nil) -} - -func NewFlightRecorderBadJMXUserKey() *operatorv1beta1.FlightRecorder { - key := "not-username" - return newFlightRecorder(&operatorv1beta1.JMXAuthSecret{ - SecretName: "test-jmx-auth", - UsernameKey: &key, - }) -} - -func NewFlightRecorderBadJMXPassKey() *operatorv1beta1.FlightRecorder { - key := "not-password" - return newFlightRecorder(&operatorv1beta1.JMXAuthSecret{ - SecretName: "test-jmx-auth", - PasswordKey: &key, - }) -} - -func NewFlightRecorderForCryostat() *operatorv1beta1.FlightRecorder { - userKey := "CRYOSTAT_RJMX_USER" - passKey := "CRYOSTAT_RJMX_PASS" - recorder := newFlightRecorder(&operatorv1beta1.JMXAuthSecret{ - SecretName: "cryostat-jmx-auth", - UsernameKey: &userKey, - PasswordKey: &passKey, - }) - recorder.Name = "cryostat-pod" - recorder.Labels = map[string]string{"app": "cryostat-pod"} - recorder.OwnerReferences[0].Name = "cryostat-pod" - recorder.Spec.RecordingSelector.MatchLabels = map[string]string{"operator.cryostat.io/flightrecorder": "cryostat-pod"} - return recorder -} - -func newFlightRecorder(jmxAuth *operatorv1beta1.JMXAuthSecret) *operatorv1beta1.FlightRecorder { - return &operatorv1beta1.FlightRecorder{ - TypeMeta: metav1.TypeMeta{ - Kind: "FlightRecorder", - APIVersion: "operator.cryostat.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Namespace: "default", - Labels: map[string]string{ - "app": "test-pod", - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "Pod", - Name: "test-pod", - UID: "", - }, - }, - }, - Spec: operatorv1beta1.FlightRecorderSpec{ - JMXCredentials: jmxAuth, - RecordingSelector: metav1.AddLabelToSelector(&metav1.LabelSelector{}, operatorv1beta1.RecordingLabel, "test-pod"), - }, - Status: operatorv1beta1.FlightRecorderStatus{ - Target: &corev1.ObjectReference{ - APIVersion: "v1", - Kind: "Pod", - Name: "test-pod", - Namespace: "default", - }, - Port: 8001, - }, - } -} - -func NewRecording() *operatorv1beta1.Recording { - return newRecording(getDuration(false), nil, nil, false) -} - -func NewContinuousRecording() *operatorv1beta1.Recording { - return newRecording(getDuration(true), nil, nil, false) -} - -func NewRunningRecording() *operatorv1beta1.Recording { - running := operatorv1beta1.RecordingStateRunning - return newRecording(getDuration(false), &running, nil, false) -} - -func NewRunningContinuousRecording() *operatorv1beta1.Recording { - running := operatorv1beta1.RecordingStateRunning - return newRecording(getDuration(true), &running, nil, false) -} - -func NewRecordingToStop() *operatorv1beta1.Recording { - running := operatorv1beta1.RecordingStateRunning - stopped := operatorv1beta1.RecordingStateStopped - return newRecording(getDuration(true), &running, &stopped, false) -} - -func NewStoppedRecordingToArchive() *operatorv1beta1.Recording { - stopped := operatorv1beta1.RecordingStateStopped - return newRecording(getDuration(false), &stopped, nil, true) -} - -func NewRecordingToStopAndArchive() *operatorv1beta1.Recording { - running := operatorv1beta1.RecordingStateRunning - stopped := operatorv1beta1.RecordingStateStopped - return newRecording(getDuration(true), &running, &stopped, true) -} - -func NewArchivedRecording() *operatorv1beta1.Recording { - stopped := operatorv1beta1.RecordingStateStopped - rec := newRecording(getDuration(false), &stopped, nil, true) - savedDownloadURL := "http://path/to/saved-test-recording.jfr" - savedReportURL := "http://path/to/saved-test-recording.html" - rec.Status.DownloadURL = &savedDownloadURL - rec.Status.ReportURL = &savedReportURL - return rec -} - -func NewDeletedArchivedRecording() *operatorv1beta1.Recording { - rec := NewArchivedRecording() - delTime := metav1.Unix(0, 1598045501618*int64(time.Millisecond)) - rec.DeletionTimestamp = &delTime - return rec -} - -func newRecording(duration time.Duration, currentState *operatorv1beta1.RecordingState, - requestedState *operatorv1beta1.RecordingState, archive bool) *operatorv1beta1.Recording { - finalizers := []string{} - status := operatorv1beta1.RecordingStatus{} - if currentState != nil { - downloadUrl := "http://path/to/test-recording.jfr" - reportUrl := "http://path/to/test-recording.html" - finalizers = append(finalizers, "operator.cryostat.io/recording.finalizer") - status = operatorv1beta1.RecordingStatus{ - State: currentState, - StartTime: metav1.Unix(0, 1597090030341*int64(time.Millisecond)), - Duration: metav1.Duration{Duration: duration}, - DownloadURL: &downloadUrl, - ReportURL: &reportUrl, - } - } - return &operatorv1beta1.Recording{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-recording", - Namespace: "default", - Finalizers: finalizers, - }, - Spec: operatorv1beta1.RecordingSpec{ - Name: "test-recording", - EventOptions: []string{ - "jdk.socketRead:enabled=true", - "jdk.socketWrite:enabled=true", - }, - Duration: metav1.Duration{Duration: duration}, - State: requestedState, - Archive: archive, - FlightRecorder: &corev1.LocalObjectReference{ - Name: "test-pod", - }, - }, - Status: status, - } -} - -func getDuration(continuous bool) time.Duration { - seconds := 0 - if !continuous { - seconds = 30 - } - return time.Duration(seconds) * time.Second -} - -func NewTargetPod() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Namespace: "default", - }, - Status: corev1.PodStatus{ - PodIP: "1.2.3.4", - }, - } -} - -func NewCryostatPod() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-pod", - Namespace: "default", - }, - Status: corev1.PodStatus{ - PodIP: "1.2.3.4", - }, - } -} - -func NewTestEndpoints() *corev1.Endpoints { - target := &corev1.ObjectReference{ - Kind: "Pod", - Name: "test-pod", - Namespace: "default", - } - ports := []corev1.EndpointPort{ - { - Name: "jfr-jmx", - Port: 1234, - }, - { - Name: "other-port", - Port: 9091, - }, - } - return newTestEndpoints(target, ports) -} - -func NewTestEndpointsNoTargetRef() *corev1.Endpoints { - ports := []corev1.EndpointPort{ - { - Name: "jfr-jmx", - Port: 1234, - }, - { - Name: "other-port", - Port: 9091, - }, - } - return newTestEndpoints(nil, ports) -} - -func NewTestEndpointsNoPorts() *corev1.Endpoints { - target := &corev1.ObjectReference{ - Kind: "Pod", - Name: "test-pod", - Namespace: "default", - } - return newTestEndpoints(target, nil) -} - -func NewTestEndpointsNoJMXPort() *corev1.Endpoints { - target := &corev1.ObjectReference{ - Kind: "Pod", - Name: "test-pod", - Namespace: "default", - } - ports := []corev1.EndpointPort{ - { - Name: "other-port", - Port: 9091, - }, - } - return newTestEndpoints(target, ports) -} - -func newTestEndpoints(targetRef *corev1.ObjectReference, ports []corev1.EndpointPort) *corev1.Endpoints { - return &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{ - { - IP: "1.2.3.4", - Hostname: "test-pod", - TargetRef: targetRef, - }, - }, - Ports: ports, - }, - }, - } -} - -func NewCryostatEndpoints() *corev1.Endpoints { - return &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{ - { - IP: "1.2.3.4", - Hostname: "cryostat-pod", - TargetRef: &corev1.ObjectReference{ - Kind: "Pod", - Name: "cryostat-pod", - Namespace: "default", - }, - }, - }, - Ports: []corev1.EndpointPort{ - { - Name: "jfr-jmx", - Port: 1234, - }, - }, - }, - }, - } -} - func NewCryostatService() *corev1.Service { c := true return &corev1.Service{ @@ -1060,32 +749,6 @@ func NewCryostatCAIssuer() *certv1.Issuer { } } -func NewJMXAuthSecret() *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jmx-auth", - Namespace: "default", - }, - Data: map[string][]byte{ - operatorv1beta1.DefaultUsernameKey: []byte("hello"), - operatorv1beta1.DefaultPasswordKey: []byte("world"), - }, - } -} - -func NewJMXAuthSecretForCryostat() *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-jmx-auth", - Namespace: "default", - }, - Data: map[string][]byte{ - operatorv1beta1.DefaultUsernameKey: []byte("hello"), - operatorv1beta1.DefaultPasswordKey: []byte("world"), - }, - } -} - func newPVC(spec *corev1.PersistentVolumeClaimSpec, labels map[string]string, annotations map[string]string) *corev1.PersistentVolumeClaim { return &corev1.PersistentVolumeClaim{ diff --git a/internal/test/server.go b/internal/test/server.go deleted file mode 100644 index f034affa..00000000 --- a/internal/test/server.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright The Cryostat Authors -// -// The Universal Permissive License (UPL), Version 1.0 -// -// Subject to the condition set forth below, permission is hereby granted to any -// person obtaining a copy of this software, associated documentation and/or data -// (collectively the "Software"), free of charge and under any and all copyright -// rights in the Software, and any and all patent rights owned or freely -// licensable by each licensor hereunder covering either (i) the unmodified -// Software as contributed to or provided by such licensor, or (ii) the Larger -// Works (as defined below), to deal in both -// -// (a) the Software, and -// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -// one is included with the Software (each a "Larger Work" to which the Software -// is contributed by such licensors), -// -// without restriction, including without limitation the rights to copy, create -// derivative works of, display, perform, and distribute the Software and make, -// use, sell, offer for sale, import, export, have made, and have sold the -// Software and the Larger Work(s), and to sublicense the foregoing rights on -// either these or other terms. -// -// This license is subject to the following condition: -// The above copyright notice and either this complete permission notice or at -// a minimum a reference to the UPL must be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package test - -import ( - "context" - "encoding/pem" - "net/http" - - certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" - certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" - "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// CryostatServer is a test HTTP server used to simulate the Cryostat -// backend in unit tests -type CryostatServer struct { - impl *ghttp.Server -} - -// NewServer creates a CryostatServer for use by unit tests -func NewServer(client client.Client, handlers []http.HandlerFunc, tls bool) *CryostatServer { - var server *ghttp.Server - if !tls { - server = ghttp.NewServer() - } else { - server = ghttp.NewTLSServer() - updateCACert(client, server) - } - server.AppendHandlers(handlers...) - return &CryostatServer{ - impl: server, - } -} - -// VerifyRequestsReceived checks that the number of requests received by the server -// match the length of the handlers argument -func (s *CryostatServer) VerifyRequestsReceived(handlers []http.HandlerFunc) { - gomega.Expect(s.impl.ReceivedRequests()).To(gomega.HaveLen(len(handlers))) -} - -// Close shuts down this test server -func (s *CryostatServer) Close() { - s.impl.Close() -} - -func updateCACert(client client.Client, server *ghttp.Server) { - ctx := context.Background() - - // Fetch CA Certificate - caCert := &certv1.Certificate{} - err := client.Get(ctx, types.NamespacedName{Name: "cryostat-ca", Namespace: "default"}, caCert) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - // Get the test server's certificate - certs := server.HTTPTestServer.TLS.Certificates - gomega.Expect(certs).To(gomega.HaveLen(1)) - rawCerts := certs[0].Certificate - gomega.Expect(rawCerts).To(gomega.HaveLen(1)) - - // Encode certificate in PEM format - certPem := &pem.Block{ - Type: "CERTIFICATE", - Bytes: rawCerts[0], - } - pemData := pem.EncodeToMemory(certPem) - - // Create corresponding Secret - secret := newCASecret(pemData) - err = client.Create(ctx, secret) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - // Update Certificate to Ready - caCert.Status.Conditions = append(caCert.Status.Conditions, certv1.CertificateCondition{ - Type: certv1.CertificateConditionReady, - Status: certMeta.ConditionTrue, - }) - err = client.Status().Update(ctx, caCert) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) -}