1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] Envoy Config Update (#1711)

This commit is contained in:
Adam Janikowski 2024-09-01 13:26:51 +02:00 committed by GitHub
parent 2a62af3d23
commit 1095567432
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 4887 additions and 937 deletions

View file

@ -19,6 +19,7 @@
- (Feature) PongV1 Integration Service
- (Feature) Custom Gateway image
- (Bugfix) Fix race condition in ArangoBackup
- (Feature) Improve Gateway Config gen
## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries

View file

@ -803,10 +803,12 @@ set-typed-api-version/%:
"$(ROOT)/pkg/deployment/" \
"$(ROOT)/pkg/replication/" \
"$(ROOT)/pkg/operator/" \
"$(ROOT)/pkg/operatorV2/" \
"$(ROOT)/pkg/server/" \
"$(ROOT)/pkg/util/" \
"$(ROOT)/pkg/handlers/" \
"$(ROOT)/pkg/apis/backup/" \
"$(ROOT)/pkg/apis/networking/" \
"$(ROOT)/pkg/upgrade/" \
| cut -d ':' -f 1 | sort | uniq \
| xargs -n 1 $(SED) -i "s#github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/typed/$*/v[A-Za-z0-9]\+#github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/typed/$*/v$(API_VERSION)#g"
@ -817,10 +819,12 @@ set-api-version/%:
"$(ROOT)/pkg/deployment/" \
"$(ROOT)/pkg/replication/" \
"$(ROOT)/pkg/operator/" \
"$(ROOT)/pkg/operatorV2/" \
"$(ROOT)/pkg/server/" \
"$(ROOT)/pkg/util/" \
"$(ROOT)/pkg/handlers/" \
"$(ROOT)/pkg/apis/backup/" \
"$(ROOT)/pkg/apis/networking/" \
"$(ROOT)/pkg/upgrade/" \
| cut -d ':' -f 1 | sort | uniq \
| xargs -n 1 $(SED) -i "s#github.com/arangodb/kube-arangodb/pkg/apis/$*/v[A-Za-z0-9]\+#github.com/arangodb/kube-arangodb/pkg/apis/$*/v$(API_VERSION)#g"
@ -828,10 +832,12 @@ set-api-version/%:
"$(ROOT)/pkg/deployment/" \
"$(ROOT)/pkg/replication/" \
"$(ROOT)/pkg/operator/" \
"$(ROOT)/pkg/operatorV2/" \
"$(ROOT)/pkg/server/" \
"$(ROOT)/pkg/util/" \
"$(ROOT)/pkg/handlers/" \
"$(ROOT)/pkg/apis/backup/" \
"$(ROOT)/pkg/apis/networking/" \
"$(ROOT)/pkg/upgrade/" \
| cut -d ':' -f 1 | sort | uniq \
| xargs -n 1 $(SED) -i "s#DatabaseV[A-Za-z0-9]\+()\.#DatabaseV$(API_VERSION)().#g"
@ -839,10 +845,12 @@ set-api-version/%:
"$(ROOT)/pkg/deployment/" \
"$(ROOT)/pkg/replication/" \
"$(ROOT)/pkg/operator/" \
"$(ROOT)/pkg/operatorV2/" \
"$(ROOT)/pkg/server/" \
"$(ROOT)/pkg/util/" \
"$(ROOT)/pkg/handlers" \
"$(ROOT)/pkg/apis/backup/" \
"$(ROOT)/pkg/apis/networking/" \
"$(ROOT)/pkg/upgrade/" \
| cut -d ':' -f 1 | sort | uniq \
| xargs -n 1 $(SED) -i "s#ReplicationV[A-Za-z0-9]\+()\.#ReplicationV$(API_VERSION)().#g"

View file

@ -13,19 +13,61 @@ metadata:
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
rules:
# analytics.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "graphanalyticsengines.analytics.arangodb.com"
# apps.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangojobs.apps.arangodb.com"
# backup.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangobackuppolicies.backup.arangodb.com"
- "arangobackups.backup.arangodb.com"
# database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeployments.database.arangodb.com"
- "arangoclustersynchronizations.database.arangodb.com"
- "arangodeployments.database.arangodb.com"
- "arangomembers.database.arangodb.com"
- "arangotasks.database.arangodb.com"
# ml.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangomlbatchjobs.ml.arangodb.com"
- "arangomlcronjobs.ml.arangodb.com"
- "arangomlextensions.ml.arangodb.com"
- "arangomlstorages.ml.arangodb.com"
# networking.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoroutes.networking.arangodb.com"
# replication.database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeploymentreplications.replication.database.arangodb.com"
- "arangobackups.backup.arangodb.com"
- "arangobackuppolicies.backup.arangodb.com"
- "arangojobs.apps.arangodb.com"
- "arangolocalstorages.storage.arangodb.com"
# scheduler.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoprofiles.scheduler.arangodb.com"
{{- end }}
{{- end }}

View file

@ -13,19 +13,61 @@ metadata:
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
rules:
# analytics.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "graphanalyticsengines.analytics.arangodb.com"
# apps.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangojobs.apps.arangodb.com"
# backup.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangobackuppolicies.backup.arangodb.com"
- "arangobackups.backup.arangodb.com"
# database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeployments.database.arangodb.com"
- "arangoclustersynchronizations.database.arangodb.com"
- "arangodeployments.database.arangodb.com"
- "arangomembers.database.arangodb.com"
- "arangotasks.database.arangodb.com"
# ml.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangomlbatchjobs.ml.arangodb.com"
- "arangomlcronjobs.ml.arangodb.com"
- "arangomlextensions.ml.arangodb.com"
- "arangomlstorages.ml.arangodb.com"
# networking.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoroutes.networking.arangodb.com"
# replication.database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeploymentreplications.replication.database.arangodb.com"
- "arangobackups.backup.arangodb.com"
- "arangobackuppolicies.backup.arangodb.com"
- "arangojobs.apps.arangodb.com"
- "arangolocalstorages.storage.arangodb.com"
# scheduler.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoprofiles.scheduler.arangodb.com"
{{- end }}
{{- end }}

View file

@ -13,19 +13,61 @@ metadata:
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
rules:
# analytics.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "graphanalyticsengines.analytics.arangodb.com"
# apps.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangojobs.apps.arangodb.com"
# backup.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangobackuppolicies.backup.arangodb.com"
- "arangobackups.backup.arangodb.com"
# database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeployments.database.arangodb.com"
- "arangoclustersynchronizations.database.arangodb.com"
- "arangodeployments.database.arangodb.com"
- "arangomembers.database.arangodb.com"
- "arangotasks.database.arangodb.com"
# ml.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangomlbatchjobs.ml.arangodb.com"
- "arangomlcronjobs.ml.arangodb.com"
- "arangomlextensions.ml.arangodb.com"
- "arangomlstorages.ml.arangodb.com"
# networking.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoroutes.networking.arangodb.com"
# replication.database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeploymentreplications.replication.database.arangodb.com"
- "arangobackups.backup.arangodb.com"
- "arangobackuppolicies.backup.arangodb.com"
- "arangojobs.apps.arangodb.com"
- "arangolocalstorages.storage.arangodb.com"
# scheduler.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoprofiles.scheduler.arangodb.com"
{{- end }}
{{- end }}

View file

@ -13,19 +13,61 @@ metadata:
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
rules:
# analytics.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "graphanalyticsengines.analytics.arangodb.com"
# apps.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangojobs.apps.arangodb.com"
# backup.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangobackuppolicies.backup.arangodb.com"
- "arangobackups.backup.arangodb.com"
# database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeployments.database.arangodb.com"
- "arangoclustersynchronizations.database.arangodb.com"
- "arangodeployments.database.arangodb.com"
- "arangomembers.database.arangodb.com"
- "arangotasks.database.arangodb.com"
# ml.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangomlbatchjobs.ml.arangodb.com"
- "arangomlcronjobs.ml.arangodb.com"
- "arangomlextensions.ml.arangodb.com"
- "arangomlstorages.ml.arangodb.com"
# networking.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoroutes.networking.arangodb.com"
# replication.database.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangodeploymentreplications.replication.database.arangodb.com"
- "arangobackups.backup.arangodb.com"
- "arangobackuppolicies.backup.arangodb.com"
- "arangojobs.apps.arangodb.com"
- "arangolocalstorages.storage.arangodb.com"
# scheduler.arangodb.com
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "update", "delete"]
resourceNames:
- "arangoprofiles.scheduler.arangodb.com"
{{- end }}
{{- end }}

View file

@ -3045,7 +3045,7 @@ Type: `boolean` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.
### .spec.gateway.enabled
Type: `boolean` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/deployment/v1/deployment_spec_gateway.go#L29)</sup>
Type: `boolean` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/deployment/v1/deployment_spec_gateway.go#L33)</sup>
Enabled setting enables/disables support for gateway in the cluster.
When enabled, the cluster will contain a number of `gateway` servers.
@ -3056,13 +3056,205 @@ Default Value: `false`
### .spec.gateway.image
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/deployment/v1/deployment_spec_gateway.go#L33)</sup>
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/deployment/v1/deployment_spec_gateway.go#L37)</sup>
Image is the image to use for the gateway.
By default, the image is determined by the operator.
***
### .spec.gateway.sidecar.args
Type: `array` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/core.go#L50)</sup>
Arguments to the entrypoint.
The container image's CMD is used if this is not provided.
Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
of whether the variable exists or not. Cannot be updated.
Links:
* [Kubernetes Docs](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell)
***
### .spec.gateway.sidecar.command
Type: `array` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/core.go#L40)</sup>
Entrypoint array. Not executed within a shell.
The container image's ENTRYPOINT is used if this is not provided.
Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
of whether the variable exists or not. Cannot be updated.
Links:
* [Kubernetes Docs](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell)
***
### .spec.gateway.sidecar.controllerListenPort
Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/integration.go#L36)</sup>
ControllerListenPort defines on which port the sidecar container will be listening for controller requests
Default Value: `9202`
***
### .spec.gateway.sidecar.env
Type: `core.EnvVar` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/environments.go#L36)</sup>
Env keeps the information about environment variables provided to the container
Links:
* [Kubernetes Docs](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#envvar-v1-core)
***
### .spec.gateway.sidecar.envFrom
Type: `core.EnvFromSource` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/environments.go#L41)</sup>
EnvFrom keeps the information about environment variable sources provided to the container
Links:
* [Kubernetes Docs](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#envfromsource-v1-core)
***
### .spec.gateway.sidecar.image
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/image.go#L35)</sup>
Image define image details
***
### .spec.gateway.sidecar.imagePullPolicy
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/image.go#L39)</sup>
ImagePullPolicy define Image pull policy
Default Value: `IfNotPresent`
***
### .spec.gateway.sidecar.lifecycle
Type: `core.Lifecycle` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/lifecycle.go#L35)</sup>
Lifecycle keeps actions that the management system should take in response to container lifecycle events.
***
### .spec.gateway.sidecar.listenPort
Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/integration.go#L32)</sup>
ListenPort defines on which port the sidecar container will be listening for connections
Default Value: `9201`
***
### .spec.gateway.sidecar.livenessProbe
Type: `core.Probe` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/probes.go#L37)</sup>
LivenessProbe keeps configuration of periodic probe of container liveness.
Container will be restarted if the probe fails.
Links:
* [Kubernetes docs](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes)
***
### .spec.gateway.sidecar.ports
Type: `[]core.ContainerPort` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/networking.go#L39)</sup>
Ports contains list of ports to expose from the container. Not specifying a port here
DOES NOT prevent that port from being exposed. Any port which is
listening on the default "0.0.0.0" address inside a container will be
accessible from the network.
***
### .spec.gateway.sidecar.readinessProbe
Type: `core.Probe` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/probes.go#L42)</sup>
ReadinessProbe keeps configuration of periodic probe of container service readiness.
Container will be removed from service endpoints if the probe fails.
Links:
* [Kubernetes docs](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes)
***
### .spec.gateway.sidecar.resources
Type: `core.ResourceRequirements` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/resources.go#L37)</sup>
Resources holds resource requests & limits for container
Links:
* [Documentation of core.ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#resourcerequirements-v1-core)
***
### .spec.gateway.sidecar.securityContext
Type: `core.SecurityContext` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/security.go#L35)</sup>
SecurityContext holds container-level security attributes and common container settings.
Links:
* [Kubernetes docs](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
***
### .spec.gateway.sidecar.startupProbe
Type: `core.Probe` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/probes.go#L50)</sup>
StartupProbe indicates that the Pod has successfully initialized.
If specified, no other probes are executed until this completes successfully.
If this probe fails, the Pod will be restarted, just as if the livenessProbe failed.
This can be used to provide different probe parameters at the beginning of a Pod's lifecycle,
when it might take a long time to load data or warm a cache, than during steady-state operation.
Links:
* [Kubernetes docs](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes)
***
### .spec.gateway.sidecar.volumeMounts
Type: `[]core.VolumeMount` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/volume_mounts.go#L35)</sup>
VolumeMounts keeps list of pod volumes to mount into the container's filesystem.
***
### .spec.gateway.sidecar.workingDir
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/scheduler/v1beta1/container/resources/core.go#L55)</sup>
Container's working directory.
If not specified, the container runtime's default will be used, which
might be configured in the container image.
***
### .spec.gateways.affinity
Type: `core.PodAffinity` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/deployment/v1/server_group_spec.go#L156)</sup>

View file

@ -12,7 +12,15 @@ title: ArangoRoute V1Alpha1
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec.go#L27)</sup>
DeploymentName specifies the ArangoDeployment object name
Deployment specifies the ArangoDeployment object name
***
### .spec.destination.path
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L36)</sup>
Path defines service path used for overrides
***
@ -123,15 +131,29 @@ UID keeps the information about object UID
***
### .status.targets\[int\].tls.insecure
### .status.target.destinations\[int\].host
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_destination.go#L38)</sup>
***
### .status.target.destinations\[int\].port
Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_destination.go#L39)</sup>
***
### .status.target.path
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L37)</sup>
Path specifies request path override
***
### .status.target.TLS.insecure
Type: `boolean` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_tls.go#L27)</sup>
Insecure allows Insecure traffic
***
### .status.targets\[int\].url
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L34)</sup>

View file

@ -0,0 +1,28 @@
apiVersion: "database.arangodb.com/v1"
kind: "ArangoDeployment"
metadata:
name: "example-simple-single"
spec:
mode: Single
image: 'arangodb/arangodb:3.12.2'
gateway:
enabled: true
gateways:
count: 1
---
apiVersion: "networking.arangodb.com/v1alpha1"
kind: "ArangoRoute"
metadata:
name: "example-simple-single-route"
spec:
deployment: example-simple-single
destination:
service:
name: example-simple-single
port: 8529
schema: https
tls:
insecure: true
path: "/_api/"
route:
path: "/secondary/"

View file

@ -175,6 +175,14 @@ func Test_GenerateAPIDocs(t *testing.T) {
"Spec": deploymentApi.ArangoMember{}.Spec,
},
},
Shared: []string{
"shared/v1",
"scheduler/v1beta1",
"scheduler/v1beta1/container",
"scheduler/v1beta1/container/resources",
"scheduler/v1beta1/pod",
"scheduler/v1beta1/pod/resources",
},
},
},
"apps": map[string]inputPackage{

View file

@ -20,7 +20,11 @@
package v1
import "github.com/arangodb/kube-arangodb/pkg/util"
import (
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util"
)
type DeploymentSpecGateway struct {
// Enabled setting enables/disables support for gateway in the cluster.
@ -31,6 +35,9 @@ type DeploymentSpecGateway struct {
// Image is the image to use for the gateway.
// By default, the image is determined by the operator.
Image *string `json:"image"`
// Sidecar define the integration sidecar spec
Sidecar *schedulerApi.IntegrationSidecar `json:"sidecar,omitempty"`
}
// IsEnabled returns whether the gateway is enabled.
@ -42,9 +49,22 @@ func (d *DeploymentSpecGateway) IsEnabled() bool {
return *d.Enabled
}
func (d *DeploymentSpecGateway) GetSidecar() *schedulerApi.IntegrationSidecar {
if d == nil || d.Sidecar == nil {
return nil
}
return d.Sidecar
}
// Validate the given spec
func (d *DeploymentSpecGateway) Validate() error {
return nil
if d == nil {
d = &DeploymentSpecGateway{}
}
return shared.WithErrors(
shared.PrefixResourceErrors("integrationSidecar", d.GetSidecar().Validate()),
)
}
// GetImage returns the image to use for the gateway.

View file

@ -230,6 +230,8 @@ func (g ServerGroup) DefaultTerminationGracePeriod() time.Duration {
return time.Hour
case ServerGroupCoordinators:
return time.Hour
case ServerGroupGateways:
return 15 * time.Minute
default:
return time.Second * 30
}

View file

@ -28,6 +28,7 @@ package v1
import (
time "time"
v1beta1 "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
sharedv1 "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -1184,6 +1185,11 @@ func (in *DeploymentSpecGateway) DeepCopyInto(out *DeploymentSpecGateway) {
*out = new(string)
**out = **in
}
if in.Sidecar != nil {
in, out := &in.Sidecar, &out.Sidecar
*out = new(v1beta1.IntegrationSidecar)
(*in).DeepCopyInto(*out)
}
return
}

View file

@ -20,7 +20,11 @@
package v2alpha1
import "github.com/arangodb/kube-arangodb/pkg/util"
import (
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util"
)
type DeploymentSpecGateway struct {
// Enabled setting enables/disables support for gateway in the cluster.
@ -31,6 +35,9 @@ type DeploymentSpecGateway struct {
// Image is the image to use for the gateway.
// By default, the image is determined by the operator.
Image *string `json:"image"`
// Sidecar define the integration sidecar spec
Sidecar *schedulerApi.IntegrationSidecar `json:"sidecar,omitempty"`
}
// IsEnabled returns whether the gateway is enabled.
@ -42,9 +49,22 @@ func (d *DeploymentSpecGateway) IsEnabled() bool {
return *d.Enabled
}
func (d *DeploymentSpecGateway) GetSidecar() *schedulerApi.IntegrationSidecar {
if d == nil || d.Sidecar == nil {
return nil
}
return d.Sidecar
}
// Validate the given spec
func (d *DeploymentSpecGateway) Validate() error {
return nil
if d == nil {
d = &DeploymentSpecGateway{}
}
return shared.WithErrors(
shared.PrefixResourceErrors("integrationSidecar", d.GetSidecar().Validate()),
)
}
// GetImage returns the image to use for the gateway.

View file

@ -230,6 +230,8 @@ func (g ServerGroup) DefaultTerminationGracePeriod() time.Duration {
return time.Hour
case ServerGroupCoordinators:
return time.Hour
case ServerGroupGateways:
return 15 * time.Minute
default:
return time.Second * 30
}

View file

@ -28,6 +28,7 @@ package v2alpha1
import (
time "time"
v1beta1 "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
sharedv1 "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -1184,6 +1185,11 @@ func (in *DeploymentSpecGateway) DeepCopyInto(out *DeploymentSpecGateway) {
*out = new(string)
**out = **in
}
if in.Sidecar != nil {
in, out := &in.Sidecar, &out.Sidecar
*out = new(v1beta1.IntegrationSidecar)
(*in).DeepCopyInto(*out)
}
return
}

View file

@ -23,8 +23,8 @@ package v1alpha1
import shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
type ArangoRouteSpec struct {
// DeploymentName specifies the ArangoDeployment object name
DeploymentName *string `json:"deployment,omitempty"`
// Deployment specifies the ArangoDeployment object name
Deployment *string `json:"deployment,omitempty"`
// Destination defines the route destination
Destination *ArangoRouteSpecDestination `json:"destination,omitempty"`
@ -33,6 +33,14 @@ type ArangoRouteSpec struct {
Route *ArangoRouteSpecRoute `json:"route,omitempty"`
}
func (s *ArangoRouteSpec) GetDeployment() string {
if s == nil || s.Destination == nil {
return ""
}
return *s.Deployment
}
func (s *ArangoRouteSpec) GetDestination() *ArangoRouteSpecDestination {
if s == nil || s.Destination == nil {
return nil
@ -54,7 +62,7 @@ func (s *ArangoRouteSpec) Validate() error {
}
if err := shared.WithErrors(shared.PrefixResourceErrors("spec",
shared.PrefixResourceErrors("deployment", shared.ValidateResourceNamePointer(s.DeploymentName)),
shared.PrefixResourceErrors("deployment", shared.ValidateResourceNamePointer(s.Deployment)),
shared.ValidateRequiredInterfacePath("destination", s.Destination),
shared.ValidateOptionalInterfacePath("route", s.Route),
)); err != nil {

View file

@ -31,30 +31,41 @@ type ArangoRouteSpecDestination struct {
// TLS defines TLS Configuration
TLS *ArangoRouteSpecDestinationTLS `json:"tls,omitempty"`
// Path defines service path used for overrides
Path *string `json:"path,omitempty"`
}
func (s *ArangoRouteSpecDestination) GetService() *ArangoRouteSpecDestinationService {
if s == nil || s.Service == nil {
func (a *ArangoRouteSpecDestination) GetService() *ArangoRouteSpecDestinationService {
if a == nil || a.Service == nil {
return nil
}
return s.Service
return a.Service
}
func (s *ArangoRouteSpecDestination) GetSchema() *ArangoRouteSpecDestinationSchema {
if s == nil || s.Schema == nil {
func (a *ArangoRouteSpecDestination) GetSchema() *ArangoRouteSpecDestinationSchema {
if a == nil || a.Schema == nil {
return nil
}
return s.Schema
return a.Schema
}
func (s *ArangoRouteSpecDestination) GetTLS() *ArangoRouteSpecDestinationTLS {
if s == nil || s.TLS == nil {
func (a *ArangoRouteSpecDestination) GetPath() string {
if a == nil || a.Path == nil {
return "/"
}
return *a.Path
}
func (a *ArangoRouteSpecDestination) GetTLS() *ArangoRouteSpecDestinationTLS {
if a == nil || a.TLS == nil {
return nil
}
return s.TLS
return a.TLS
}
func (a *ArangoRouteSpecDestination) Validate() error {
@ -66,6 +77,7 @@ func (a *ArangoRouteSpecDestination) Validate() error {
shared.ValidateOptionalInterfacePath("service", a.Service),
shared.ValidateOptionalInterfacePath("schema", a.Schema),
shared.ValidateOptionalInterfacePath("tls", a.TLS),
shared.PrefixResourceError("path", shared.ValidateAPIPath(a.GetPath())),
); err != nil {
return err
}

View file

@ -31,7 +31,7 @@ type ArangoRouteSpecRoute struct {
func (a *ArangoRouteSpecRoute) GetPath() string {
if a == nil || a.Path == nil {
return "/"
return ""
}
return *a.Path
@ -43,7 +43,7 @@ func (a *ArangoRouteSpecRoute) Validate() error {
}
if err := shared.WithErrors(
shared.PrefixResourceError("path", shared.ValidateAPIPath(a.GetPath())),
shared.ValidateRequiredPath("path", a.Path, shared.ValidateAPIPath),
); err != nil {
return err
}

View file

@ -33,6 +33,6 @@ type ArangoRouteStatus struct {
// Deployment keeps the ArangoDeployment reference
Deployment *sharedApi.Object `json:"deployment,omitempty"`
// Targets keeps the target details
Targets ArangoRouteStatusTargets `json:"targets,omitempty"`
// Target keeps the target details
Target *ArangoRouteStatusTarget `json:"target,omitempty"`
}

View file

@ -20,25 +20,46 @@
package v1alpha1
import "github.com/arangodb/kube-arangodb/pkg/util"
import (
"fmt"
type ArangoRouteStatusTargets []ArangoRouteStatusTarget
func (a ArangoRouteStatusTargets) Hash() string {
return util.SHA256FromExtract(func(t ArangoRouteStatusTarget) string {
return t.Hash()
}, a...)
}
"github.com/arangodb/kube-arangodb/pkg/util"
)
type ArangoRouteStatusTarget struct {
Url string `json:"url,omitempty"`
// Destinations keeps target destinations
Destinations ArangoRouteStatusTargetDestinations `json:"destinations,omitempty"`
TLS ArangoRouteStatusTargetTLS `json:"tls,omitempty"`
// TLS Keeps target TLS Settings (if not nil, TLS is enabled)
TLS *ArangoRouteStatusTargetTLS `json:"TLS,omitempty"`
// Path specifies request path override
Path string `json:"path,omitempty"`
}
func (a *ArangoRouteStatusTarget) RenderURLs() []string {
if a == nil {
return nil
}
var urls = make([]string, len(a.Destinations))
proto := "http"
if a.TLS != nil {
proto = "https"
}
for id, dest := range a.Destinations {
urls[id] = fmt.Sprintf("%s://%s:%d%s", proto, dest.Host, dest.Port, a.Path)
}
return urls
}
func (a *ArangoRouteStatusTarget) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromStringArray(a.Url, a.TLS.Hash())
return util.SHA256FromStringArray(a.Destinations.Hash(), a.TLS.Hash(), a.Path)
}

View file

@ -0,0 +1,47 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1alpha1
import (
"fmt"
"github.com/arangodb/kube-arangodb/pkg/util"
)
type ArangoRouteStatusTargetDestinations []ArangoRouteStatusTargetDestination
func (a ArangoRouteStatusTargetDestinations) Hash() string {
return util.SHA256FromExtract(func(t ArangoRouteStatusTargetDestination) string {
return t.Hash()
}, a...)
}
type ArangoRouteStatusTargetDestination struct {
Host string `json:"host,omitempty"`
Port int32 `json:"port,omitempty"`
}
func (a *ArangoRouteStatusTargetDestination) Hash() string {
if a == nil {
return ""
}
return util.SHA256FromStringArray(fmt.Sprintf("%s:%d", a.Host, a.Port))
}

View file

@ -24,7 +24,7 @@ import "github.com/arangodb/kube-arangodb/pkg/util"
type ArangoRouteStatusTargetTLS struct {
// Insecure allows Insecure traffic
Insecure bool `json:"insecure"`
Insecure *bool `json:"insecure"`
}
func (a *ArangoRouteStatusTargetTLS) Hash() string {
@ -32,5 +32,13 @@ func (a *ArangoRouteStatusTargetTLS) Hash() string {
return ""
}
return util.SHA256FromStringArray(util.BoolSwitch(a.Insecure, "true", "false"))
return util.SHA256FromStringArray(util.BoolSwitch(a.IsInsecure(), "true", "false"))
}
func (a *ArangoRouteStatusTargetTLS) IsInsecure() bool {
if a == nil || a.Insecure == nil {
return false
}
return *a.Insecure
}

View file

@ -26,7 +26,7 @@
package v1alpha1
import (
deploymentv1 "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
v2alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
v1 "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
@ -96,8 +96,8 @@ func (in *ArangoRouteList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteSpec) DeepCopyInto(out *ArangoRouteSpec) {
*out = *in
if in.DeploymentName != nil {
in, out := &in.DeploymentName, &out.DeploymentName
if in.Deployment != nil {
in, out := &in.Deployment, &out.Deployment
*out = new(string)
**out = **in
}
@ -142,6 +142,11 @@ func (in *ArangoRouteSpecDestination) DeepCopyInto(out *ArangoRouteSpecDestinati
*out = new(ArangoRouteSpecDestinationTLS)
(*in).DeepCopyInto(*out)
}
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(string)
**out = **in
}
return
}
@ -228,7 +233,7 @@ func (in *ArangoRouteStatus) DeepCopyInto(out *ArangoRouteStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(deploymentv1.ConditionList, len(*in))
*out = make(v2alpha1.ConditionList, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@ -238,10 +243,10 @@ func (in *ArangoRouteStatus) DeepCopyInto(out *ArangoRouteStatus) {
*out = new(v1.Object)
(*in).DeepCopyInto(*out)
}
if in.Targets != nil {
in, out := &in.Targets, &out.Targets
*out = make(ArangoRouteStatusTargets, len(*in))
copy(*out, *in)
if in.Target != nil {
in, out := &in.Target, &out.Target
*out = new(ArangoRouteStatusTarget)
(*in).DeepCopyInto(*out)
}
return
}
@ -259,7 +264,16 @@ func (in *ArangoRouteStatus) DeepCopy() *ArangoRouteStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteStatusTarget) DeepCopyInto(out *ArangoRouteStatusTarget) {
*out = *in
out.TLS = in.TLS
if in.Destinations != nil {
in, out := &in.Destinations, &out.Destinations
*out = make(ArangoRouteStatusTargetDestinations, len(*in))
copy(*out, *in)
}
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(ArangoRouteStatusTargetTLS)
(*in).DeepCopyInto(*out)
}
return
}
@ -273,9 +287,50 @@ func (in *ArangoRouteStatusTarget) DeepCopy() *ArangoRouteStatusTarget {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteStatusTargetDestination) DeepCopyInto(out *ArangoRouteStatusTargetDestination) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargetDestination.
func (in *ArangoRouteStatusTargetDestination) DeepCopy() *ArangoRouteStatusTargetDestination {
if in == nil {
return nil
}
out := new(ArangoRouteStatusTargetDestination)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ArangoRouteStatusTargetDestinations) DeepCopyInto(out *ArangoRouteStatusTargetDestinations) {
{
in := &in
*out = make(ArangoRouteStatusTargetDestinations, len(*in))
copy(*out, *in)
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargetDestinations.
func (in ArangoRouteStatusTargetDestinations) DeepCopy() ArangoRouteStatusTargetDestinations {
if in == nil {
return nil
}
out := new(ArangoRouteStatusTargetDestinations)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteStatusTargetTLS) DeepCopyInto(out *ArangoRouteStatusTargetTLS) {
*out = *in
if in.Insecure != nil {
in, out := &in.Insecure, &out.Insecure
*out = new(bool)
**out = **in
}
return
}
@ -288,23 +343,3 @@ func (in *ArangoRouteStatusTargetTLS) DeepCopy() *ArangoRouteStatusTargetTLS {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ArangoRouteStatusTargets) DeepCopyInto(out *ArangoRouteStatusTargets) {
{
in := &in
*out = make(ArangoRouteStatusTargets, len(*in))
copy(*out, *in)
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargets.
func (in ArangoRouteStatusTargets) DeepCopy() ArangoRouteStatusTargets {
if in == nil {
return nil
}
out := new(ArangoRouteStatusTargets)
in.DeepCopyInto(out)
return *out
}

View file

@ -59,9 +59,9 @@ func (m *Metadata) Apply(template *core.PodTemplateSpec) error {
z := m.DeepCopy()
template.Labels = z.Labels
template.Annotations = z.Annotations
template.OwnerReferences = z.OwnerReferences
template.Labels = util.MergeMaps(true, template.Labels, z.Labels)
template.Annotations = util.MergeMaps(true, template.Annotations, z.Annotations)
template.OwnerReferences = append(template.OwnerReferences, z.OwnerReferences...)
return nil
}

View file

@ -131,6 +131,49 @@ func Test_Metadata(t *testing.T) {
require.Contains(t, pod.Annotations, "B2")
require.EqualValues(t, "4", pod.Annotations["B2"])
require.Len(t, pod.OwnerReferences, 2)
require.EqualValues(t, "test", pod.OwnerReferences[0].UID)
require.EqualValues(t, "test2", pod.OwnerReferences[1].UID)
})
})
t.Run("Update Templat", func(t *testing.T) {
applyMetadata(t, &core.PodTemplateSpec{
ObjectMeta: meta.ObjectMeta{
Labels: map[string]string{
"A": "1",
},
Annotations: map[string]string{
"B": "2",
},
OwnerReferences: []meta.OwnerReference{
{
UID: "test",
},
},
},
}, &Metadata{
Labels: map[string]string{
"A": "3",
},
Annotations: map[string]string{
"B2": "4",
},
OwnerReferences: []meta.OwnerReference{
{
UID: "test2",
},
},
})(func(t *testing.T, pod *core.PodTemplateSpec) {
require.Len(t, pod.Labels, 1)
require.Contains(t, pod.Labels, "A")
require.EqualValues(t, "3", pod.Labels["A"])
require.Len(t, pod.Annotations, 2)
require.Contains(t, pod.Annotations, "B")
require.EqualValues(t, "2", pod.Annotations["B"])
require.Contains(t, pod.Annotations, "B2")
require.EqualValues(t, "4", pod.Annotations["B2"])
require.Len(t, pod.OwnerReferences, 2)
require.EqualValues(t, "test", pod.OwnerReferences[0].UID)
require.EqualValues(t, "test2", pod.OwnerReferences[1].UID)

View file

@ -59,9 +59,9 @@ func (m *Metadata) Apply(template *core.PodTemplateSpec) error {
z := m.DeepCopy()
template.Labels = z.Labels
template.Annotations = z.Annotations
template.OwnerReferences = z.OwnerReferences
template.Labels = util.MergeMaps(true, template.Labels, z.Labels)
template.Annotations = util.MergeMaps(true, template.Annotations, z.Annotations)
template.OwnerReferences = append(template.OwnerReferences, z.OwnerReferences...)
return nil
}

View file

@ -131,6 +131,49 @@ func Test_Metadata(t *testing.T) {
require.Contains(t, pod.Annotations, "B2")
require.EqualValues(t, "4", pod.Annotations["B2"])
require.Len(t, pod.OwnerReferences, 2)
require.EqualValues(t, "test", pod.OwnerReferences[0].UID)
require.EqualValues(t, "test2", pod.OwnerReferences[1].UID)
})
})
t.Run("Update Template", func(t *testing.T) {
applyMetadata(t, &core.PodTemplateSpec{
ObjectMeta: meta.ObjectMeta{
Labels: map[string]string{
"A": "1",
},
Annotations: map[string]string{
"B": "2",
},
OwnerReferences: []meta.OwnerReference{
{
UID: "test",
},
},
},
}, &Metadata{
Labels: map[string]string{
"A": "3",
},
Annotations: map[string]string{
"B2": "4",
},
OwnerReferences: []meta.OwnerReference{
{
UID: "test2",
},
},
})(func(t *testing.T, pod *core.PodTemplateSpec) {
require.Len(t, pod.Labels, 1)
require.Contains(t, pod.Labels, "A")
require.EqualValues(t, "3", pod.Labels["A"])
require.Len(t, pod.Annotations, 2)
require.Contains(t, pod.Annotations, "B")
require.EqualValues(t, "2", pod.Annotations["B"])
require.Contains(t, pod.Annotations, "B2")
require.EqualValues(t, "4", pod.Annotations["B2"])
require.Len(t, pod.OwnerReferences, 2)
require.EqualValues(t, "test", pod.OwnerReferences[0].UID)
require.EqualValues(t, "test2", pod.OwnerReferences[1].UID)

View file

@ -34,7 +34,7 @@ import (
var (
resourceNameRE = regexp.MustCompile(`^([0-9\-\.a-z])+$`)
apiPathRE = regexp.MustCompile(`^/([A-Za-z0-9\-]+/)*$`)
apiPathRE = regexp.MustCompile(`^/([_A-Za-z0-9\-]+/)*$`)
)
const (
@ -190,6 +190,19 @@ func ValidateList[T any](in []T, validator func(T) error) error {
return WithErrors(errors...)
}
// ValidateMap validates all elements on the list
func ValidateMap[T any](in map[string]T, validator func(string, T) error) error {
errors := make([]error, 0, len(in))
for id := range in {
if err := PrefixResourceError(fmt.Sprintf("`%s`", id), validator(id, in[id])); err != nil {
errors = append(errors, err)
}
}
return WithErrors(errors...)
}
// ValidateImage Validates if provided image is valid
func ValidateImage(image string) error {
if image == "" {

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,14 @@ v1alpha1:
spec:
properties:
deployment:
description: DeploymentName specifies the ArangoDeployment object name
description: Deployment specifies the ArangoDeployment object name
type: string
destination:
description: Destination defines the route destination
properties:
path:
description: Path defines service path used for overrides
type: string
schema:
description: Schema defines HTTP/S schema used for connection
type: string

View file

@ -31,6 +31,7 @@ import (
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
@ -283,7 +284,7 @@ func (i *ImageUpdatePod) GetRole() string {
return "id"
}
func (i *ImageUpdatePod) Init(_ context.Context, _ interfaces.Inspector, pod *core.Pod) error {
func (i *ImageUpdatePod) Init(_ context.Context, _ interfaces.Inspector, pod *core.PodTemplateSpec) error {
terminationGracePeriodSeconds := int64((time.Second * 30).Seconds())
pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds
pod.Spec.PriorityClassName = i.spec.ID.Get().PriorityClassName
@ -311,7 +312,7 @@ func (i *ImageUpdatePod) GetVolumes() []core.Volume {
return getVolumes(i.AsInput()).Volumes()
}
func (i *ImageUpdatePod) GetSidecars(*core.Pod) error {
func (i *ImageUpdatePod) GetSidecars(spec *core.PodTemplateSpec) error {
return nil
}
@ -503,6 +504,10 @@ func (a *ImageUpdatePod) AsInput() pod.Input {
}
}
func (i *ImageUpdatePod) Profiles() (schedulerApi.ProfileTemplates, error) {
return nil, nil
}
// GetExecutor returns the fixed path to the ArangoSync binary in the container.
func (a *ArangoSyncIdentity) GetExecutor() string {
return resources.ArangoSyncExecutor

View file

@ -132,6 +132,8 @@ func (s *stateInspector) RefreshState(ctx context.Context, members api.Deploymen
if results[id].IsServing() {
client = results[id].client
}
case api.ServerGroupTypeGateway:
results[id] = s.fetchGatewayMemberState(ctxChild, members[id])
default:
assertion.InvalidGroupKey.Assert(true, "Unable to fetch Health for an unknown group: %s", members[id].Group.AsRole())
results[id] = State{
@ -178,6 +180,9 @@ func (s *stateInspector) RefreshState(ctx context.Context, members api.Deploymen
case api.ServerGroupTypeArangoSync:
// ArangoSync is considered as healthy when it is possible to fetch version.
results[i].IsClusterHealthy = true
case api.ServerGroupTypeGateway:
// Gateway is considered as healthy when it is possible to fetch version.
results[i].IsClusterHealthy = true
default:
assertion.InvalidGroupKey.Assert(true, "Unable to fetch Health for an unknown group: %s", members[i].Group.AsRole())
results[i].IsClusterHealthy = false
@ -228,6 +233,26 @@ func (s *stateInspector) fetchArangosyncMemberState(ctx context.Context, m api.D
return state
}
func (s *stateInspector) fetchGatewayMemberState(ctx context.Context, m api.DeploymentStatusMemberElement) State {
// by default, it is not serving. It will be changed if it serves.
var state State
c, err := s.deployment.GetServerClient(ctx, m.Group, m.Member.ID)
if err != nil {
state.NotReachableErr = err
return state
}
if v, err := c.Version(ctx); err != nil {
state.NotReachableErr = err
return state
} else {
state.Version = v
state.client = c
}
return state
}
func (s *stateInspector) fetchServerMemberState(ctx context.Context, m api.DeploymentStatusMemberElement,
servingGroup api.ServerGroup) State {
// by default, it is not serving. It will be changed if it serves.

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -81,4 +81,9 @@ var probeMap = map[api.ServerGroup]probes{
liveness: newProbe(true, true),
readiness: newProbe(false, false),
},
api.ServerGroupGateways: { // TODO: Enable Probes
startup: newProbe(false, false),
liveness: newProbe(false, false),
readiness: newProbe(false, false),
},
}

View file

@ -29,6 +29,7 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tolerations"
@ -78,7 +79,7 @@ func (a actionRuntimeContainerSyncTolerations) Start(ctx context.Context) (bool,
expectedTolerations := member.Spec.Template.PodSpec.Spec.Tolerations
origTolerations := tolerations.CreatePodTolerations(a.actionCtx.GetMode(), a.action.Group)
origTolerations := resources.CreatePodTolerations(a.actionCtx.GetMode(), a.action.Group)
calculatedTolerations := tolerations.MergeTolerationsIfNotFound(currentTolerations, origTolerations, expectedTolerations)

View file

@ -0,0 +1,182 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package resources
import (
"context"
"fmt"
"path/filepath"
core "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources/gateway"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
configMapsV1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/configmap/v1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/patcher"
)
func (r *Resources) ensureGatewayConfig(ctx context.Context, cachedStatus inspectorInterface.Inspector, configMaps configMapsV1.ModInterface) error {
deploymentName := r.context.GetAPIObject().GetName()
configMapName := GetGatewayConfigMapName(deploymentName)
log := r.log.Str("section", "gateway-config").Str("name", configMapName)
cfg, err := r.renderGatewayConfig(cachedStatus)
if err != nil {
return errors.WithStack(errors.Wrapf(err, "Failed to generate gateway config"))
}
gatewayCfgYaml, gatewayCfgChecksum, _, err := cfg.RenderYAML()
if err != nil {
return errors.WithStack(errors.Wrapf(err, "Failed to render gateway config"))
}
if cm, exists := cachedStatus.ConfigMap().V1().GetSimple(configMapName); !exists {
// Create
cm = &core.ConfigMap{
ObjectMeta: meta.ObjectMeta{
Name: configMapName,
},
Data: map[string]string{
GatewayConfigFileName: string(gatewayCfgYaml),
},
}
owner := r.context.GetAPIObject().AsOwner()
err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
return k8sutil.CreateConfigMap(ctxChild, configMaps, cm, &owner)
})
if kerrors.IsAlreadyExists(err) {
// CM added while we tried it also
return nil
} else if err != nil {
// Failed to create
return errors.WithStack(err)
}
return errors.Reconcile()
} else {
// CM Exists, checks checksum - if key is not in the map we return empty string
if existingSha := util.SHA256FromString(cm.Data[GatewayConfigFileName]); existingSha != gatewayCfgChecksum {
// We need to do the update
if _, changed, err := patcher.Patcher[*core.ConfigMap](ctx, cachedStatus.ConfigMapsModInterface().V1(), cm, meta.PatchOptions{},
patcher.PatchConfigMapData(map[string]string{
GatewayConfigFileName: string(gatewayCfgYaml),
})); err != nil {
log.Err(err).Debug("Failed to patch GatewayConfig ConfigMap")
return errors.WithStack(err)
} else if changed {
log.Str("service", cm.GetName()).Str("before", existingSha).Str("after", gatewayCfgChecksum).Info("Updated GatewayConfig")
}
}
}
return nil
}
func (r *Resources) renderGatewayConfig(cachedStatus inspectorInterface.Inspector) (gateway.Config, error) {
deploymentName := r.context.GetAPIObject().GetName()
log := r.log.Str("section", "gateway-config-render")
spec := r.context.GetSpec()
svcServingName := fmt.Sprintf("%s-%s", deploymentName, spec.Mode.Get().ServingGroup().AsRole())
svc, svcExist := cachedStatus.Service().V1().GetSimple(svcServingName)
if !svcExist {
return gateway.Config{}, errors.Errorf("Service %s not found", svcServingName)
}
var cfg gateway.Config
cfg.DefaultDestination = gateway.ConfigDestination{
Targets: []gateway.ConfigDestinationTarget{
{
Host: svc.Spec.ClusterIP,
Port: shared.ArangoPort,
},
},
}
if spec.TLS.IsSecure() {
// Enabled TLS, add config
keyPath := filepath.Join(shared.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile)
cfg.DefaultTLS = &gateway.ConfigTLS{
CertificatePath: keyPath,
PrivateKeyPath: keyPath,
}
cfg.DefaultDestination.Type = util.NewType(gateway.ConfigDestinationTypeHTTPS)
}
// Check ArangoRoutes
if c, err := cachedStatus.ArangoRoute().V1Alpha1(); err == nil {
cfg.Destinations = gateway.ConfigDestinations{}
if err := c.Iterate(func(at *networkingApi.ArangoRoute) error {
log := log.Str("ArangoRoute", at.GetName())
if !at.Status.Conditions.IsTrue(networkingApi.ReadyCondition) {
l := log
if c, ok := at.Status.Conditions.Get(networkingApi.ReadyCondition); ok {
l.Str("message", c.Message)
}
l.Warn("ArangoRoute is not ready")
return nil
}
if target := at.Status.Target; target != nil {
var dest gateway.ConfigDestination
if destinations := target.Destinations; len(destinations) > 0 {
for _, destination := range destinations {
var t gateway.ConfigDestinationTarget
t.Host = destination.Host
t.Port = destination.Port
dest.Targets = append(dest.Targets, t)
}
}
if tls := target.TLS; tls != nil {
dest.Type = util.NewType(gateway.ConfigDestinationTypeHTTPS)
}
dest.Path = util.NewType(target.Path)
cfg.Destinations[at.Spec.GetRoute().GetPath()] = dest
}
return nil
}, func(at *networkingApi.ArangoRoute) bool {
return at.Spec.GetDeployment() == deploymentName
}); err != nil {
return gateway.Config{}, errors.Wrapf(err, "Unable to iterate over ArangoRoutes")
}
}
return cfg, nil
}

View file

@ -22,21 +22,13 @@ package resources
import (
"context"
"fmt"
"time"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/metrics"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
configMapsV1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/configmap/v1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
)
var (
@ -65,49 +57,3 @@ func (r *Resources) EnsureConfigMaps(ctx context.Context, cachedStatus inspector
}
return reconcileRequired.Reconcile(ctx)
}
func (r *Resources) ensureGatewayConfig(ctx context.Context, cachedStatus inspectorInterface.Inspector, configMaps configMapsV1.ModInterface) error {
deploymentName := r.context.GetAPIObject().GetName()
configMapName := GetGatewayConfigMapName(deploymentName)
if _, exists := cachedStatus.ConfigMap().V1().GetSimple(configMapName); !exists {
// Find serving service (single/crdn)
spec := r.context.GetSpec()
svcServingName := fmt.Sprintf("%s-%s", deploymentName, spec.Mode.Get().ServingGroup().AsRole())
svc, svcExist := cachedStatus.Service().V1().GetSimple(svcServingName)
if !svcExist {
return errors.Errorf("Service %s not found", svcServingName)
}
gatewayCfgYaml, err := RenderGatewayConfigYAML(svc.Spec.ClusterIP)
if err != nil {
return errors.WithStack(errors.Wrapf(err, "Failed to render gateway config"))
}
cm := &core.ConfigMap{
ObjectMeta: meta.ObjectMeta{
Name: configMapName,
},
Data: map[string]string{
GatewayConfigFileName: string(gatewayCfgYaml),
GatewayConfigChecksumName: util.SHA256(gatewayCfgYaml),
},
}
owner := r.context.GetAPIObject().AsOwner()
err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
return k8sutil.CreateConfigMap(ctxChild, configMaps, cm, &owner)
})
if kerrors.IsAlreadyExists(err) {
// CM added while we tried it also
return nil
} else if err != nil {
// Failed to create
return errors.WithStack(err)
}
return errors.Reconcile()
}
return nil
}

View file

@ -0,0 +1,259 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
"fmt"
"sort"
bootstrapAPI "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
clusterAPI "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
coreAPI "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
listenerAPI "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
routeAPI "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
routerAPI "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
httpConnectionManagerAPI "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb"
"sigs.k8s.io/yaml"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type Config struct {
DefaultDestination ConfigDestination `json:"defaultDestination,omitempty"`
Destinations ConfigDestinations `json:"destinations,omitempty"`
DefaultTLS *ConfigTLS `json:"defaultTLS,omitempty"`
}
func (c Config) Validate() error {
return errors.Errors(
shared.PrefixResourceErrors("defaultDestination", c.DefaultDestination.Validate()),
shared.PrefixResourceErrors("destinations", c.Destinations.Validate()),
)
}
func (c Config) RenderYAML() ([]byte, string, *bootstrapAPI.Bootstrap, error) {
cfg, err := c.Render()
if err != nil {
return nil, "", nil, err
}
data, err := protojson.MarshalOptions{
UseProtoNames: true,
}.Marshal(cfg)
if err != nil {
return nil, "", nil, err
}
data, err = yaml.JSONToYAML(data)
return data, util.SHA256(data), cfg, err
}
func (c Config) Render() (*bootstrapAPI.Bootstrap, error) {
if err := c.Validate(); err != nil {
return nil, errors.Wrapf(err, "Validation failed")
}
clusters, err := c.RenderClusters()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render clusters")
}
listener, err := c.RenderListener()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render listener")
}
return &bootstrapAPI.Bootstrap{
Admin: &bootstrapAPI.Admin{
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: "127.0.0.1",
PortSpecifier: &coreAPI.SocketAddress_PortValue{PortValue: 9901},
},
},
},
},
StaticResources: &bootstrapAPI.Bootstrap_StaticResources{
Listeners: []*listenerAPI.Listener{
listener,
},
Clusters: clusters,
},
}, nil
}
func (c Config) RenderClusters() ([]*clusterAPI.Cluster, error) {
def, err := c.DefaultDestination.RenderCluster("default")
if err != nil {
return nil, err
}
clusters := []*clusterAPI.Cluster{
def,
}
for k, v := range c.Destinations {
name := fmt.Sprintf("cluster_%s", util.SHA256FromString(k))
c, err := v.RenderCluster(name)
if err != nil {
return nil, err
}
clusters = append(clusters, c)
}
sort.Slice(clusters, func(i, j int) bool {
return clusters[i].Name < clusters[j].Name
})
return clusters, nil
}
func (c Config) RenderRoutes() ([]*routeAPI.Route, error) {
def, err := c.DefaultDestination.RenderRoute("default", "/")
if err != nil {
return nil, err
}
routes := []*routeAPI.Route{
def,
}
for k, v := range c.Destinations {
name := fmt.Sprintf("cluster_%s", util.SHA256FromString(k))
c, err := v.RenderRoute(name, k)
if err != nil {
return nil, err
}
routes = append(routes, c)
}
sort.Slice(routes, func(i, j int) bool {
return routes[i].GetMatch().GetPrefix() > routes[j].GetMatch().GetPrefix()
})
return routes, nil
}
func (c Config) RenderFilters() ([]*listenerAPI.Filter, error) {
httpFilterConfigType, err := anypb.New(&routerAPI.Router{})
if err != nil {
return nil, errors.Wrapf(err, "Unable to render route config")
}
routes, err := c.RenderRoutes()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render routes")
}
filterConfigType, err := anypb.New(&httpConnectionManagerAPI.HttpConnectionManager{
StatPrefix: "ingress_http",
CodecType: httpConnectionManagerAPI.HttpConnectionManager_AUTO,
RouteSpecifier: &httpConnectionManagerAPI.HttpConnectionManager_RouteConfig{
RouteConfig: &routeAPI.RouteConfiguration{
Name: "default",
VirtualHosts: []*routeAPI.VirtualHost{
{
Name: "default",
Domains: []string{"*"},
Routes: routes,
},
},
},
},
HttpFilters: []*httpConnectionManagerAPI.HttpFilter{
{
Name: "envoy.filters.http.routerAPI",
ConfigType: &httpConnectionManagerAPI.HttpFilter_TypedConfig{
TypedConfig: httpFilterConfigType,
},
},
},
})
if err != nil {
return nil, errors.Wrapf(err, "Unable to render http connection manager")
}
return []*listenerAPI.Filter{
{
Name: "envoy.filters.network.httpConnectionManagerAPI",
ConfigType: &listenerAPI.Filter_TypedConfig{
TypedConfig: filterConfigType,
},
},
}, nil
}
func (c Config) RenderDefaultFilterChain() (*listenerAPI.FilterChain, error) {
filters, err := c.RenderFilters()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render filters")
}
ret := &listenerAPI.FilterChain{
Filters: filters,
}
if tls, err := c.DefaultTLS.RenderListenerTransportSocket(); err != nil {
return nil, err
} else {
ret.TransportSocket = tls
}
return ret, nil
}
func (c Config) RenderSecondaryFilterChains() ([]*listenerAPI.FilterChain, error) {
return nil, nil
}
func (c Config) RenderListener() (*listenerAPI.Listener, error) {
filterChains, err := c.RenderSecondaryFilterChains()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render secondary filter chains")
}
defaultFilterChain, err := c.RenderDefaultFilterChain()
if err != nil {
return nil, errors.Wrapf(err, "Unable to render default filter")
}
return &listenerAPI.Listener{
Name: "default",
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: "0.0.0.0",
PortSpecifier: &coreAPI.SocketAddress_PortValue{PortValue: shared.ArangoPort},
},
},
},
FilterChains: filterChains,
DefaultFilterChain: defaultFilterChain,
}, nil
}

View file

@ -0,0 +1,122 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
"time"
clusterAPI "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
endpointAPI "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
routeAPI "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"google.golang.org/protobuf/types/known/durationpb"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type ConfigDestinations map[string]ConfigDestination
func (c ConfigDestinations) Validate() error {
if len(c) == 0 {
return nil
}
return shared.WithErrors(
shared.ValidateMap(c, func(k string, destination ConfigDestination) error {
var errs []error
if k == "/" {
errs = append(errs, errors.Errorf("Route for `/` is reserved"))
}
if err := shared.ValidateAPIPath(k); err != nil {
errs = append(errs, err)
}
if err := destination.Validate(); err != nil {
errs = append(errs, err)
}
return shared.WithErrors(errs...)
}),
)
}
type ConfigDestination struct {
Targets ConfigDestinationTargets `json:"targets,omitempty"`
Type *ConfigDestinationType `json:"type,omitempty"`
Path *string `json:"path,omitempty"`
}
func (c ConfigDestination) Validate() error {
return shared.WithErrors(
shared.PrefixResourceError("targets", c.Targets.Validate()),
shared.PrefixResourceError("type", c.Type.Validate()),
shared.PrefixResourceError("path", shared.ValidateAPIPath(c.GetPath())),
)
}
func (c ConfigDestination) GetPath() string {
if c.Path == nil {
return "/"
}
return *c.Path
}
func (c ConfigDestination) RenderRoute(name, prefix string) (*routeAPI.Route, error) {
return &routeAPI.Route{
Match: &routeAPI.RouteMatch{
PathSpecifier: &routeAPI.RouteMatch_Prefix{
Prefix: prefix,
},
},
Action: &routeAPI.Route_Route{
Route: &routeAPI.RouteAction{
ClusterSpecifier: &routeAPI.RouteAction_Cluster{
Cluster: name,
},
PrefixRewrite: c.GetPath(),
},
},
}, nil
}
func (c ConfigDestination) RenderCluster(name string) (*clusterAPI.Cluster, error) {
cluster := &clusterAPI.Cluster{
Name: name,
ConnectTimeout: durationpb.New(time.Second),
LbPolicy: clusterAPI.Cluster_ROUND_ROBIN,
LoadAssignment: &endpointAPI.ClusterLoadAssignment{
ClusterName: name,
Endpoints: []*endpointAPI.LocalityLbEndpoints{
{
LbEndpoints: c.Targets.RenderEndpoints(),
},
},
},
}
if t, err := c.Type.RenderUpstreamTransportSocket(); err != nil {
return nil, err
} else {
cluster.TransportSocket = t
}
return cluster, nil
}

View file

@ -0,0 +1,92 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
coreAPI "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
endpointAPI "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type ConfigDestinationTargets []ConfigDestinationTarget
func (c ConfigDestinationTargets) RenderEndpoints() []*endpointAPI.LbEndpoint {
var endpoints = make([]*endpointAPI.LbEndpoint, len(c))
for id := range c {
endpoints[id] = c[id].RenderEndpoint()
}
return endpoints
}
func (c ConfigDestinationTargets) Validate() error {
if len(c) == 0 {
return errors.Errorf("Empty Target not allowed")
}
return shared.ValidateList(c, func(target ConfigDestinationTarget) error {
return target.Validate()
})
}
type ConfigDestinationTarget struct {
Host string `json:"ip,omitempty"`
Port int32 `json:"port,omitempty"`
}
func (c ConfigDestinationTarget) Validate() error {
return shared.WithErrors(
shared.ValidateRequiredPath("ip", &c.Host, func(t string) error {
if t == "" {
return errors.Errorf("Empty string not allowed")
}
return nil
}),
shared.ValidateRequiredPath("ip", &c.Port, func(t int32) error {
if t <= 0 {
return errors.Errorf("Port needs to be greater than 0")
}
return nil
}),
)
}
func (c ConfigDestinationTarget) RenderEndpoint() *endpointAPI.LbEndpoint {
return &endpointAPI.LbEndpoint{
HostIdentifier: &endpointAPI.LbEndpoint_Endpoint{
Endpoint: &endpointAPI.Endpoint{
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Protocol: coreAPI.SocketAddress_TCP,
Address: c.Host,
PortSpecifier: &coreAPI.SocketAddress_PortValue{
PortValue: uint32(c.Port),
},
},
},
},
},
},
}
}

View file

@ -0,0 +1,80 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
coreAPI "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
tlsApi "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"google.golang.org/protobuf/types/known/anypb"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type ConfigDestinationType int
const (
ConfigDestinationTypeHTTP ConfigDestinationType = iota
ConfigDestinationTypeHTTPS
)
func (c *ConfigDestinationType) Get() ConfigDestinationType {
if c == nil {
return ConfigDestinationTypeHTTP
}
switch v := *c; v {
case ConfigDestinationTypeHTTP, ConfigDestinationTypeHTTPS:
return v
default:
return ConfigDestinationTypeHTTP
}
}
func (c *ConfigDestinationType) RenderUpstreamTransportSocket() (*coreAPI.TransportSocket, error) {
if c.Get() == ConfigDestinationTypeHTTPS {
tlsConfig, err := anypb.New(&tlsApi.UpstreamTlsContext{
CommonTlsContext: &tlsApi.CommonTlsContext{
ValidationContextType: &tlsApi.CommonTlsContext_ValidationContext{},
},
})
if err != nil {
return nil, err
}
return &coreAPI.TransportSocket{
Name: "envoy.transport_sockets.tls",
ConfigType: &coreAPI.TransportSocket_TypedConfig{
TypedConfig: tlsConfig,
},
}, nil
}
return nil, nil
}
func (c *ConfigDestinationType) Validate() error {
switch c.Get() {
case ConfigDestinationTypeHTTP, ConfigDestinationTypeHTTPS:
return nil
default:
return errors.Errorf("Invalid destination type")
}
}

View file

@ -0,0 +1,150 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
"testing"
bootstrapAPI "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
"github.com/stretchr/testify/require"
"github.com/arangodb/kube-arangodb/pkg/util"
)
func renderAndPrintGatewayConfig(t *testing.T, cfg Config) *bootstrapAPI.Bootstrap {
data, checksum, obj, err := cfg.RenderYAML()
require.NoError(t, err)
t.Logf("Checksum: %s", checksum)
t.Log(string(data))
return obj
}
func Test_GatewayConfig(t *testing.T) {
t.Run("Default", func(t *testing.T) {
renderAndPrintGatewayConfig(t, Config{
DefaultDestination: ConfigDestination{
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12345,
},
},
},
})
})
t.Run("Default", func(t *testing.T) {
renderAndPrintGatewayConfig(t, Config{
DefaultDestination: ConfigDestination{
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12345,
},
},
Type: util.NewType(ConfigDestinationTypeHTTPS),
},
DefaultTLS: &ConfigTLS{
CertificatePath: "/test",
PrivateKeyPath: "/test12",
},
})
})
t.Run("Default", func(t *testing.T) {
renderAndPrintGatewayConfig(t, Config{
DefaultDestination: ConfigDestination{
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12345,
},
},
Path: util.NewType("/test/path/"),
Type: util.NewType(ConfigDestinationTypeHTTPS),
},
DefaultTLS: &ConfigTLS{
CertificatePath: "/test",
PrivateKeyPath: "/test12",
},
})
})
t.Run("Default", func(t *testing.T) {
renderAndPrintGatewayConfig(t, Config{
DefaultDestination: ConfigDestination{
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12345,
},
},
Path: util.NewType("/test/path/"),
Type: util.NewType(ConfigDestinationTypeHTTPS),
},
DefaultTLS: &ConfigTLS{
CertificatePath: "/test",
PrivateKeyPath: "/test12",
},
Destinations: ConfigDestinations{
"/test/": {
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12346,
},
},
Path: util.NewType("/test/path/"),
Type: util.NewType(ConfigDestinationTypeHTTPS),
},
},
})
})
t.Run("Default", func(t *testing.T) {
renderAndPrintGatewayConfig(t, Config{
DefaultDestination: ConfigDestination{
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12345,
},
},
Path: util.NewType("/test/path/"),
Type: util.NewType(ConfigDestinationTypeHTTPS),
},
DefaultTLS: &ConfigTLS{
CertificatePath: "/test",
PrivateKeyPath: "/test12",
},
Destinations: ConfigDestinations{
"/_test/": {
Targets: []ConfigDestinationTarget{
{
Host: "127.0.0.1",
Port: 12346,
},
},
Path: util.NewType("/test/path/"),
Type: util.NewType(ConfigDestinationTypeHTTP),
},
},
})
})
}

View file

@ -0,0 +1,69 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package gateway
import (
coreAPI "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
tlsApi "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"google.golang.org/protobuf/types/known/anypb"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
type ConfigTLS struct {
CertificatePath string `json:"certificatePath,omitempty"`
PrivateKeyPath string `json:"privateKeyPath,omitempty"`
}
func (c *ConfigTLS) RenderListenerTransportSocket() (*coreAPI.TransportSocket, error) {
if c == nil {
return nil, nil
}
tlsContext, err := anypb.New(&tlsApi.DownstreamTlsContext{
CommonTlsContext: &tlsApi.CommonTlsContext{
TlsCertificates: []*tlsApi.TlsCertificate{
{
CertificateChain: &coreAPI.DataSource{
Specifier: &coreAPI.DataSource_Filename{
Filename: c.CertificatePath,
},
},
PrivateKey: &coreAPI.DataSource{
Specifier: &coreAPI.DataSource_Filename{
Filename: c.PrivateKeyPath,
},
},
},
},
},
})
if err != nil {
return nil, errors.Wrapf(err, "Unable to render tls context")
}
return &coreAPI.TransportSocket{
Name: "envoy.transport_sockets.tls",
ConfigType: &coreAPI.TransportSocket_TypedConfig{
TypedConfig: tlsContext,
},
}, nil
}

View file

@ -1,268 +0,0 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package resources
import (
"fmt"
"net/url"
"strconv"
"time"
bootstrapAPI "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
clusterAPI "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
coreAPI "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
endpointAPI "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
listenerAPI "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
routeAPI "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
routerAPI "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
httpConnectionManagerAPI "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"sigs.k8s.io/yaml"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util"
)
type Redirect util.KV[string, []string]
func WithRedirect(prefix string, target ...string) Redirect {
return Redirect{
K: prefix,
V: target,
}
}
func RenderGatewayConfigYAML(dbServiceAddress string, redirects ...Redirect) ([]byte, error) {
cfg, err := RenderConfig(dbServiceAddress, redirects...)
if err != nil {
return nil, err
}
data, err := protojson.MarshalOptions{
UseProtoNames: true,
}.Marshal(cfg)
if err != nil {
return nil, err
}
data, err = yaml.JSONToYAML(data)
return data, err
}
func RenderConfig(dbServiceAddress string, redirects ...Redirect) (*bootstrapAPI.Bootstrap, error) {
clusters := []*clusterAPI.Cluster{
{
Name: "arangodb",
ConnectTimeout: durationpb.New(250 * time.Millisecond),
LbPolicy: clusterAPI.Cluster_ROUND_ROBIN,
LoadAssignment: &endpointAPI.ClusterLoadAssignment{
ClusterName: "arangodb",
Endpoints: []*endpointAPI.LocalityLbEndpoints{
{
LbEndpoints: []*endpointAPI.LbEndpoint{
{
HostIdentifier: &endpointAPI.LbEndpoint_Endpoint{
Endpoint: &endpointAPI.Endpoint{
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: dbServiceAddress,
PortSpecifier: &coreAPI.SocketAddress_PortValue{
PortValue: shared.ArangoPort,
},
},
},
},
},
},
},
},
},
},
},
},
}
routes := []*routeAPI.Route{
{
Match: &routeAPI.RouteMatch{
PathSpecifier: &routeAPI.RouteMatch_Prefix{
Prefix: "/",
},
},
Action: &routeAPI.Route_Route{
Route: &routeAPI.RouteAction{
ClusterSpecifier: &routeAPI.RouteAction_Cluster{
Cluster: "arangodb",
},
PrefixRewrite: "/",
},
},
},
}
for id, redirect := range redirects {
var endpoints []*endpointAPI.LbEndpoint
for _, target := range redirect.V {
req, err := url.Parse(target)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(req.Port())
if err != nil {
return nil, err
}
endpoints = append(endpoints, &endpointAPI.LbEndpoint{
HostIdentifier: &endpointAPI.LbEndpoint_Endpoint{
Endpoint: &endpointAPI.Endpoint{
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: req.Hostname(),
PortSpecifier: &coreAPI.SocketAddress_PortValue{
PortValue: uint32(port),
},
},
},
},
},
},
},
)
}
cluster := &clusterAPI.Cluster{
Name: fmt.Sprintf("cluster_%05d", id),
ConnectTimeout: durationpb.New(250 * time.Millisecond),
LbPolicy: clusterAPI.Cluster_ROUND_ROBIN,
LoadAssignment: &endpointAPI.ClusterLoadAssignment{
ClusterName: fmt.Sprintf("cluster_%05d", id),
Endpoints: []*endpointAPI.LocalityLbEndpoints{
{
LbEndpoints: endpoints,
},
},
},
}
route := &routeAPI.Route{
Match: &routeAPI.RouteMatch{
PathSpecifier: &routeAPI.RouteMatch_Prefix{
Prefix: redirect.K,
},
},
Action: &routeAPI.Route_Route{
Route: &routeAPI.RouteAction{
ClusterSpecifier: &routeAPI.RouteAction_Cluster{
Cluster: fmt.Sprintf("cluster_%05d", id),
},
PrefixRewrite: "/",
},
},
}
clusters = append(clusters, cluster)
routes = append(routes, route)
}
routes = util.Sort(routes, func(i, j *routeAPI.Route) bool {
return i.Match.GetPrefix() > j.Match.GetPrefix()
})
httpFilterConfigType, err := anypb.New(&routerAPI.Router{})
if err != nil {
return nil, err
}
filterConfigType, err := anypb.New(&httpConnectionManagerAPI.HttpConnectionManager{
StatPrefix: "ingress_http",
CodecType: httpConnectionManagerAPI.HttpConnectionManager_AUTO,
RouteSpecifier: &httpConnectionManagerAPI.HttpConnectionManager_RouteConfig{
RouteConfig: &routeAPI.RouteConfiguration{
Name: "local_route",
VirtualHosts: []*routeAPI.VirtualHost{
{
Name: "local_service",
Domains: []string{"*"},
Routes: routes,
},
},
},
},
HttpFilters: []*httpConnectionManagerAPI.HttpFilter{
{
Name: "envoy.filters.http.routerAPI",
ConfigType: &httpConnectionManagerAPI.HttpFilter_TypedConfig{
TypedConfig: httpFilterConfigType,
},
},
},
})
if err != nil {
return nil, err
}
return &bootstrapAPI.Bootstrap{
Admin: &bootstrapAPI.Admin{
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: "127.0.0.1",
PortSpecifier: &coreAPI.SocketAddress_PortValue{PortValue: 9901},
},
},
},
},
StaticResources: &bootstrapAPI.Bootstrap_StaticResources{
Listeners: []*listenerAPI.Listener{
{
Name: "listener_0",
Address: &coreAPI.Address{
Address: &coreAPI.Address_SocketAddress{
SocketAddress: &coreAPI.SocketAddress{
Address: "0.0.0.0",
PortSpecifier: &coreAPI.SocketAddress_PortValue{PortValue: shared.ArangoPort},
},
},
},
FilterChains: []*listenerAPI.FilterChain{
{
Filters: []*listenerAPI.Filter{
{
Name: "envoy.filters.network.httpConnectionManagerAPI",
ConfigType: &listenerAPI.Filter_TypedConfig{
TypedConfig: filterConfigType,
},
},
},
},
},
},
},
Clusters: clusters,
},
}, nil
}

View file

@ -286,10 +286,15 @@ func createArangoSyncArgs(apiObject meta.Object, spec api.DeploymentSpec, group
return args
}
func createArangoGatewayArgs(groupSpec api.ServerGroupSpec) []string {
args := []string{"--config-path", GatewayConfigFilePath}
if len(groupSpec.Args) > 0 {
args = append(args, groupSpec.Args...)
func createArangoGatewayArgs(input pod.Input, additionalOptions ...k8sutil.OptionPair) []string {
options := k8sutil.CreateOptionPairs(64)
options.Add("--config-path", GatewayConfigFilePath)
options.Append(additionalOptions...)
args := options.Sort().AsSplittedArgs()
if len(input.GroupSpec.Args) > 0 {
args = append(args, input.GroupSpec.Args...)
}
return args
@ -297,7 +302,7 @@ func createArangoGatewayArgs(groupSpec api.ServerGroupSpec) []string {
// CreatePodTolerations creates a list of tolerations for a pod created for the given group.
func (r *Resources) CreatePodTolerations(group api.ServerGroup, groupSpec api.ServerGroupSpec) []core.Toleration {
return tolerations.MergeTolerationsIfNotFound(tolerations.CreatePodTolerations(r.context.GetMode(), group), groupSpec.GetTolerations())
return tolerations.MergeTolerationsIfNotFound(CreatePodTolerations(r.context.GetMode(), group), groupSpec.GetTolerations())
}
func (r *Resources) RenderPodTemplateForMember(ctx context.Context, acs sutil.ACS, spec api.DeploymentSpec, status api.DeploymentStatus, memberID string, imageInfo api.ImageInfo) (*core.PodTemplateSpec, error) {
@ -396,14 +401,15 @@ func (r *Resources) RenderPodForMember(ctx context.Context, acs sutil.ACS, spec
podCreator = &MemberGatewayPod{
podName: podName,
status: m,
groupSpec: groupSpec,
spec: spec,
group: group,
resources: r,
imageInfo: imageInfo,
context: r.context,
deploymentStatus: status,
arangoMember: *member,
apiObject: apiObject,
memberStatus: m,
cachedStatus: cache,
}
default:
@ -687,7 +693,18 @@ func RenderArangoPod(ctx context.Context, cachedStatus inspectorInterface.Inspec
PodAffinity: podCreator.GetPodAffinity(),
}
return &p, nil
if profiles, err := podCreator.Profiles(); err != nil {
return nil, err
} else if len(profiles) > 0 {
if err := profiles.RenderOnTemplate(&p); err != nil {
return nil, err
}
}
return &core.Pod{
ObjectMeta: p.ObjectMeta,
Spec: p.Spec,
}, nil
}
// CreateArangoPod creates a new Pod with container provided by parameter 'containerCreator'

View file

@ -30,6 +30,7 @@ import (
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
@ -321,7 +322,7 @@ func (m *MemberArangoDPod) AsInput() pod.Input {
}
}
func (m *MemberArangoDPod) Init(_ context.Context, _ interfaces.Inspector, pod *core.Pod) error {
func (m *MemberArangoDPod) Init(_ context.Context, _ interfaces.Inspector, pod *core.PodTemplateSpec) error {
terminationGracePeriodSeconds := int64(math.Ceil(m.groupSpec.GetTerminationGracePeriod(m.group).Seconds()))
pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds
pod.Spec.PriorityClassName = m.groupSpec.PriorityClassName
@ -409,7 +410,7 @@ func (m *MemberArangoDPod) GetServiceAccountName() string {
return m.groupSpec.GetServiceAccountName()
}
func (m *MemberArangoDPod) GetSidecars(pod *core.Pod) error {
func (m *MemberArangoDPod) GetSidecars(pod *core.PodTemplateSpec) error {
//nolint:staticcheck
if m.spec.Metrics.IsEnabled() && m.spec.Metrics.Mode.Get() != api.MetricsModeInternal {
var c *core.Container
@ -595,6 +596,10 @@ func (m *MemberArangoDPod) Annotations() map[string]string {
return collection.MergeAnnotations(m.spec.Annotations, m.groupSpec.Annotations)
}
func (m *MemberArangoDPod) Profiles() (schedulerApi.ProfileTemplates, error) {
return nil, nil
}
func (m *MemberArangoDPod) Labels() map[string]string {
l := collection.ReservedLabels().Filter(collection.MergeAnnotations(m.spec.Labels, m.groupSpec.Labels))

View file

@ -21,21 +21,12 @@
package resources
import (
"context"
"fmt"
"math"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/util/collection"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
kresources "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
)
const (
@ -43,298 +34,22 @@ const (
GatewayVolumeMountDir = "/etc/gateway/"
GatewayVolumeName = "gateway"
GatewayConfigFileName = "gateway.yaml"
GatewayConfigChecksumName = "gateway.yaml-checksum"
GatewayConfigFilePath = GatewayVolumeMountDir + GatewayConfigFileName
)
type ArangoGatewayContainer struct {
groupSpec api.ServerGroupSpec
spec api.DeploymentSpec
group api.ServerGroup
resources *Resources
imageInfo api.ImageInfo
apiObject meta.Object
memberStatus api.MemberStatus
arangoMember api.ArangoMember
}
var _ interfaces.PodCreator = &MemberGatewayPod{}
var _ interfaces.ContainerCreator = &ArangoGatewayContainer{}
type MemberGatewayPod struct {
podName string
groupSpec api.ServerGroupSpec
spec api.DeploymentSpec
group api.ServerGroup
arangoMember api.ArangoMember
resources *Resources
imageInfo api.ImageInfo
apiObject meta.Object
memberStatus api.MemberStatus
cachedStatus interfaces.Inspector
}
func GetGatewayConfigMapName(name string) string {
return fmt.Sprintf("%s-gateway", name)
}
func (a *ArangoGatewayContainer) GetCommand() ([]string, error) {
cmd := make([]string, 0, 128)
cmd = append(cmd, a.GetExecutor())
cmd = append(cmd, createArangoGatewayArgs(a.groupSpec)...)
return cmd, nil
}
func (a *ArangoGatewayContainer) GetName() string {
return shared.ServerContainerName
}
func (a *ArangoGatewayContainer) GetPorts() []core.ContainerPort {
port := shared.ArangoPort
return []core.ContainerPort{
{
Name: shared.ServerContainerName,
ContainerPort: int32(port),
Protocol: core.ProtocolTCP,
},
}
}
func (a *ArangoGatewayContainer) GetExecutor() string {
return a.groupSpec.GetEntrypoint(ArangoGatewayExecutor)
}
func (a *ArangoGatewayContainer) GetSecurityContext() *core.SecurityContext {
return k8sutil.CreateSecurityContext(a.groupSpec.SecurityContext)
}
func (a *ArangoGatewayContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
var liveness, readiness, startup *core.Probe
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeStartupConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
if probeLivenessConfig != nil {
liveness = probeLivenessConfig.Create()
}
if probeReadinessConfig != nil {
readiness = probeReadinessConfig.Create()
}
if probeStartupConfig != nil {
startup = probeStartupConfig.Create()
}
return liveness, readiness, startup, nil
}
func (a *ArangoGatewayContainer) GetResourceRequirements() core.ResourceRequirements {
return kresources.ExtractPodAcceptedResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
}
func (a *ArangoGatewayContainer) GetLifecycle() (*core.Lifecycle, error) {
return k8sutil.NewLifecycleFinalizers()
}
func (a *ArangoGatewayContainer) GetImagePullPolicy() core.PullPolicy {
return a.spec.GetImagePullPolicy()
}
func (a *ArangoGatewayContainer) GetImage() string {
return a.imageInfo.Image
}
func (a *ArangoGatewayContainer) GetEnvs() ([]core.EnvVar, []core.EnvFromSource) {
envs := NewEnvBuilder()
envs.Add(true, k8sutil.GetLifecycleEnv()...)
if len(a.groupSpec.Envs) > 0 {
for _, env := range a.groupSpec.Envs {
// Do not override preset envs
envs.Add(false, core.EnvVar{
Name: env.Name,
Value: env.Value,
})
}
}
return envs.GetEnvList(), nil
}
func (a *ArangoGatewayContainer) GetVolumeMounts() []core.VolumeMount {
return createGatewayVolumes(a.apiObject.GetName()).VolumeMounts()
}
func (m *MemberGatewayPod) GetName() string {
return m.resources.context.GetAPIObject().GetName()
}
func (m *MemberGatewayPod) GetRole() string {
return m.group.AsRole()
}
func (m *MemberGatewayPod) GetImagePullSecrets() []string {
return m.spec.ImagePullSecrets
}
func (m *MemberGatewayPod) GetPodAntiAffinity() *core.PodAntiAffinity {
a := &core.PodAntiAffinity{}
pod.AppendPodAntiAffinityDefault(m, a)
a = kresources.MergePodAntiAffinity(a, m.groupSpec.AntiAffinity)
return kresources.OptionalPodAntiAffinity(a)
}
func (m *MemberGatewayPod) GetPodAffinity() *core.PodAffinity {
a := &core.PodAffinity{}
pod.AppendAffinityWithRole(m, a, api.ServerGroupDBServers.AsRole())
a = kresources.MergePodAffinity(a, m.groupSpec.Affinity)
return kresources.OptionalPodAffinity(a)
}
func (m *MemberGatewayPod) GetNodeAffinity() *core.NodeAffinity {
a := &core.NodeAffinity{}
pod.AppendArchSelector(a, m.memberStatus.Architecture.Default(m.spec.Architecture.GetDefault()).AsNodeSelectorRequirement())
a = kresources.MergeNodeAffinity(a, m.groupSpec.NodeAffinity)
return kresources.OptionalNodeAffinity(a)
}
func (m *MemberGatewayPod) GetNodeSelector() map[string]string {
return m.groupSpec.GetNodeSelector()
}
func (m *MemberGatewayPod) GetServiceAccountName() string {
return m.groupSpec.GetServiceAccountName()
}
func (m *MemberGatewayPod) GetSidecars(pod *core.Pod) error {
// A sidecar provided by the user
sidecars := m.groupSpec.GetSidecars()
if len(sidecars) > 0 {
addLifecycleSidecar(m.groupSpec.SidecarCoreNames, sidecars)
pod.Spec.Containers = append(pod.Spec.Containers, sidecars...)
}
return nil
}
func (m *MemberGatewayPod) GetVolumes() []core.Volume {
return createGatewayVolumes(m.apiObject.GetName()).Volumes()
}
func (m *MemberGatewayPod) IsDeploymentMode() bool {
return m.spec.IsDevelopment()
}
func (m *MemberGatewayPod) GetInitContainers(cachedStatus interfaces.Inspector) ([]core.Container, error) {
var initContainers []core.Container
if c := m.groupSpec.InitContainers.GetContainers(); len(c) > 0 {
initContainers = append(initContainers, c...)
}
res := kresources.ExtractPodInitContainerAcceptedResourceRequirement(m.GetContainerCreator().GetResourceRequirements())
initContainers = applyInitContainersResourceResources(initContainers, res)
initContainers = upscaleInitContainersResourceResources(initContainers, res)
return initContainers, nil
}
func (m *MemberGatewayPod) GetFinalizers() []string {
return nil
}
func (m *MemberGatewayPod) GetTolerations() []core.Toleration {
return m.resources.CreatePodTolerations(m.group, m.groupSpec)
}
func (m *MemberGatewayPod) GetContainerCreator() interfaces.ContainerCreator {
return &ArangoGatewayContainer{
groupSpec: m.groupSpec,
spec: m.spec,
group: m.group,
resources: m.resources,
imageInfo: m.imageInfo,
apiObject: m.apiObject,
memberStatus: m.memberStatus,
arangoMember: m.arangoMember,
}
}
func (m *MemberGatewayPod) GetRestartPolicy() core.RestartPolicy {
if features.RestartPolicyAlways().Enabled() {
return core.RestartPolicyAlways
}
return core.RestartPolicyNever
}
func (m *MemberGatewayPod) Init(ctx context.Context, cachedStatus interfaces.Inspector, pod *core.Pod) error {
terminationGracePeriodSeconds := int64(math.Ceil(m.groupSpec.GetTerminationGracePeriod(m.group).Seconds()))
pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds
pod.Spec.PriorityClassName = m.groupSpec.PriorityClassName
return nil
}
func (m *MemberGatewayPod) Validate(_ interfaces.Inspector) error {
if err := validateSidecars(m.groupSpec.SidecarCoreNames, m.groupSpec.GetSidecars()); err != nil {
return err
}
return nil
}
func (m *MemberGatewayPod) ApplyPodSpec(spec *core.PodSpec) error {
if s := m.groupSpec.SchedulerName; s != nil {
spec.SchedulerName = *s
}
m.groupSpec.PodModes.Apply(spec)
return nil
}
func (m *MemberGatewayPod) Annotations() map[string]string {
return collection.MergeAnnotations(m.spec.Annotations, m.groupSpec.Annotations)
}
func (m *MemberGatewayPod) Labels() map[string]string {
return collection.ReservedLabels().Filter(collection.MergeAnnotations(m.spec.Labels, m.groupSpec.Labels))
}
func createGatewayVolumes(memberName string) pod.Volumes {
func createGatewayVolumes(input pod.Input) pod.Volumes {
volumes := pod.NewVolumes()
volumes.AddVolume(k8sutil.LifecycleVolume())
volumes.AddVolumeMount(k8sutil.LifecycleVolumeMount())
volumes.AddVolume(k8sutil.CreateVolumeWithConfigMap(GatewayVolumeName, GetGatewayConfigMapName(memberName)))
volumes.AddVolume(k8sutil.CreateVolumeWithConfigMap(GatewayVolumeName, GetGatewayConfigMapName(input.ApiObject.GetName())))
volumes.AddVolumeMount(GatewayVolumeMount())
// TLS
volumes.Append(pod.TLS(), input)
return volumes
}

View file

@ -0,0 +1,149 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package resources
import (
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
kresources "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
)
var _ interfaces.ContainerCreator = &ArangoGatewayContainer{}
type ArangoGatewayContainer struct {
member *MemberGatewayPod
resources *Resources
groupSpec api.ServerGroupSpec
spec api.DeploymentSpec
group api.ServerGroup
arangoMember api.ArangoMember
imageInfo api.ImageInfo
cachedStatus interfaces.Inspector
input pod.Input
status api.MemberStatus
}
func (a *ArangoGatewayContainer) GetCommand() ([]string, error) {
cmd := make([]string, 0, 128)
cmd = append(cmd, a.GetExecutor())
cmd = append(cmd, createArangoGatewayArgs(a.input)...)
return cmd, nil
}
func (a *ArangoGatewayContainer) GetName() string {
return shared.ServerContainerName
}
func (a *ArangoGatewayContainer) GetPorts() []core.ContainerPort {
port := shared.ArangoPort
return []core.ContainerPort{
{
Name: shared.ServerContainerName,
ContainerPort: int32(port),
Protocol: core.ProtocolTCP,
},
}
}
func (a *ArangoGatewayContainer) GetExecutor() string {
return a.groupSpec.GetEntrypoint(ArangoGatewayExecutor)
}
func (a *ArangoGatewayContainer) GetSecurityContext() *core.SecurityContext {
return k8sutil.CreateSecurityContext(a.groupSpec.SecurityContext)
}
func (a *ArangoGatewayContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
var liveness, readiness, startup *core.Probe
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeStartupConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
if probeLivenessConfig != nil {
liveness = probeLivenessConfig.Create()
}
if probeReadinessConfig != nil {
readiness = probeReadinessConfig.Create()
}
if probeStartupConfig != nil {
startup = probeStartupConfig.Create()
}
return liveness, readiness, startup, nil
}
func (a *ArangoGatewayContainer) GetResourceRequirements() core.ResourceRequirements {
return kresources.ExtractPodAcceptedResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
}
func (a *ArangoGatewayContainer) GetLifecycle() (*core.Lifecycle, error) {
return k8sutil.NewLifecycleFinalizers()
}
func (a *ArangoGatewayContainer) GetImagePullPolicy() core.PullPolicy {
return a.spec.GetImagePullPolicy()
}
func (a *ArangoGatewayContainer) GetImage() string {
return a.imageInfo.Image
}
func (a *ArangoGatewayContainer) GetEnvs() ([]core.EnvVar, []core.EnvFromSource) {
envs := NewEnvBuilder()
envs.Add(true, k8sutil.GetLifecycleEnv()...)
if len(a.groupSpec.Envs) > 0 {
for _, env := range a.groupSpec.Envs {
// Do not override preset envs
envs.Add(false, core.EnvVar{
Name: env.Name,
Value: env.Value,
})
}
}
return envs.GetEnvList(), nil
}
func (a *ArangoGatewayContainer) GetVolumeMounts() []core.VolumeMount {
return createGatewayVolumes(a.input).VolumeMounts()
}

View file

@ -0,0 +1,242 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package resources
import (
"context"
"fmt"
"math"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container/resources"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/integrations/sidecar"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/collection"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
kresources "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
)
var _ interfaces.PodCreator = &MemberGatewayPod{}
type MemberGatewayPod struct {
podName string
status api.MemberStatus
groupSpec api.ServerGroupSpec
spec api.DeploymentSpec
deploymentStatus api.DeploymentStatus
group api.ServerGroup
arangoMember api.ArangoMember
context Context
resources *Resources
imageInfo api.ImageInfo
cachedStatus interfaces.Inspector
}
func (m *MemberGatewayPod) GetName() string {
return m.resources.context.GetAPIObject().GetName()
}
func (m *MemberGatewayPod) GetRole() string {
return m.group.AsRole()
}
func (m *MemberGatewayPod) GetImagePullSecrets() []string {
return m.spec.ImagePullSecrets
}
func (m *MemberGatewayPod) GetPodAntiAffinity() *core.PodAntiAffinity {
a := &core.PodAntiAffinity{}
pod.AppendPodAntiAffinityDefault(m, a)
a = kresources.MergePodAntiAffinity(a, m.groupSpec.AntiAffinity)
return kresources.OptionalPodAntiAffinity(a)
}
func (m *MemberGatewayPod) AsInput() pod.Input {
return pod.Input{
ApiObject: m.context.GetAPIObject(),
Deployment: m.spec,
Status: m.deploymentStatus,
Group: m.group,
GroupSpec: m.groupSpec,
Version: m.imageInfo.ArangoDBVersion,
Enterprise: m.imageInfo.Enterprise,
Member: m.status,
ArangoMember: m.arangoMember,
}
}
func (m *MemberGatewayPod) GetPodAffinity() *core.PodAffinity {
a := &core.PodAffinity{}
pod.AppendAffinityWithRole(m, a, api.ServerGroupDBServers.AsRole())
a = kresources.MergePodAffinity(a, m.groupSpec.Affinity)
return kresources.OptionalPodAffinity(a)
}
func (m *MemberGatewayPod) GetNodeAffinity() *core.NodeAffinity {
a := &core.NodeAffinity{}
pod.AppendArchSelector(a, m.status.Architecture.Default(m.spec.Architecture.GetDefault()).AsNodeSelectorRequirement())
a = kresources.MergeNodeAffinity(a, m.groupSpec.NodeAffinity)
return kresources.OptionalNodeAffinity(a)
}
func (m *MemberGatewayPod) GetNodeSelector() map[string]string {
return m.groupSpec.GetNodeSelector()
}
func (m *MemberGatewayPod) GetServiceAccountName() string {
return m.groupSpec.GetServiceAccountName()
}
func (m *MemberGatewayPod) GetSidecars(pod *core.PodTemplateSpec) error {
// A sidecar provided by the user
sidecars := m.groupSpec.GetSidecars()
if len(sidecars) > 0 {
addLifecycleSidecar(m.groupSpec.SidecarCoreNames, sidecars)
pod.Spec.Containers = append(pod.Spec.Containers, sidecars...)
}
return nil
}
func (m *MemberGatewayPod) GetVolumes() []core.Volume {
return createGatewayVolumes(m.AsInput()).Volumes()
}
func (m *MemberGatewayPod) IsDeploymentMode() bool {
return m.spec.IsDevelopment()
}
func (m *MemberGatewayPod) GetInitContainers(cachedStatus interfaces.Inspector) ([]core.Container, error) {
var initContainers []core.Container
if c := m.groupSpec.InitContainers.GetContainers(); len(c) > 0 {
initContainers = append(initContainers, c...)
}
res := kresources.ExtractPodInitContainerAcceptedResourceRequirement(m.GetContainerCreator().GetResourceRequirements())
initContainers = applyInitContainersResourceResources(initContainers, res)
initContainers = upscaleInitContainersResourceResources(initContainers, res)
return initContainers, nil
}
func (m *MemberGatewayPod) GetFinalizers() []string {
return nil
}
func (m *MemberGatewayPod) GetTolerations() []core.Toleration {
return m.resources.CreatePodTolerations(m.group, m.groupSpec)
}
func (m *MemberGatewayPod) GetContainerCreator() interfaces.ContainerCreator {
return &ArangoGatewayContainer{
member: m,
spec: m.spec,
group: m.group,
resources: m.resources,
imageInfo: m.imageInfo,
groupSpec: m.groupSpec,
arangoMember: m.arangoMember,
cachedStatus: m.cachedStatus,
input: m.AsInput(),
status: m.status,
}
}
func (m *MemberGatewayPod) GetRestartPolicy() core.RestartPolicy {
if features.RestartPolicyAlways().Enabled() {
return core.RestartPolicyAlways
}
return core.RestartPolicyNever
}
func (m *MemberGatewayPod) Init(ctx context.Context, cachedStatus interfaces.Inspector, pod *core.PodTemplateSpec) error {
terminationGracePeriodSeconds := int64(math.Ceil(m.groupSpec.GetTerminationGracePeriod(m.group).Seconds()))
pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds
pod.Spec.PriorityClassName = m.groupSpec.PriorityClassName
return nil
}
func (m *MemberGatewayPod) Validate(_ interfaces.Inspector) error {
if err := validateSidecars(m.groupSpec.SidecarCoreNames, m.groupSpec.GetSidecars()); err != nil {
return err
}
return nil
}
func (m *MemberGatewayPod) ApplyPodSpec(spec *core.PodSpec) error {
if s := m.groupSpec.SchedulerName; s != nil {
spec.SchedulerName = *s
}
m.groupSpec.PodModes.Apply(spec)
return nil
}
func (m *MemberGatewayPod) Annotations() map[string]string {
return collection.MergeAnnotations(m.spec.Annotations, m.groupSpec.Annotations)
}
func (m *MemberGatewayPod) Labels() map[string]string {
l := collection.ReservedLabels().Filter(collection.MergeAnnotations(m.spec.Labels, m.groupSpec.Labels))
if m.status.Topology != nil && m.deploymentStatus.Topology.Enabled() && m.deploymentStatus.Topology.ID == m.status.Topology.ID {
if l == nil {
l = map[string]string{}
}
l[k8sutil.LabelKeyArangoZone] = fmt.Sprintf("%d", m.status.Topology.Zone)
l[k8sutil.LabelKeyArangoTopology] = string(m.status.Topology.ID)
}
return l
}
func (m *MemberGatewayPod) Profiles() (schedulerApi.ProfileTemplates, error) {
integration, err := sidecar.NewIntegration(&schedulerContainerResourcesApi.Image{
Image: util.NewType(m.resources.context.GetOperatorImage()),
}, m.spec.Gateway.GetSidecar(), []string{shared.ServerContainerName})
if err != nil {
return nil, err
}
return []*schedulerApi.ProfileTemplate{integration}, nil
}

View file

@ -32,6 +32,7 @@ import (
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
@ -266,7 +267,7 @@ func (m *MemberSyncPod) GetServiceAccountName() string {
return m.groupSpec.GetServiceAccountName()
}
func (m *MemberSyncPod) GetSidecars(pod *core.Pod) error {
func (m *MemberSyncPod) GetSidecars(pod *core.PodTemplateSpec) error {
// A sidecar provided by the user
sidecars := m.groupSpec.GetSidecars()
if len(sidecars) > 0 {
@ -350,7 +351,7 @@ func (m *MemberSyncPod) GetRestartPolicy() core.RestartPolicy {
}
// Init initializes the arangosync pod.
func (m *MemberSyncPod) Init(ctx context.Context, cachedStatus interfaces.Inspector, pod *core.Pod) error {
func (m *MemberSyncPod) Init(ctx context.Context, cachedStatus interfaces.Inspector, pod *core.PodTemplateSpec) error {
terminationGracePeriodSeconds := int64(math.Ceil(m.groupSpec.GetTerminationGracePeriod(m.group).Seconds()))
pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds
pod.Spec.PriorityClassName = m.groupSpec.PriorityClassName
@ -517,3 +518,7 @@ func (m *MemberSyncPod) syncHostAlias() *core.HostAlias {
return &alias
}
func (m *MemberSyncPod) Profiles() (schedulerApi.ProfileTemplates, error) {
return nil, nil
}

View file

@ -0,0 +1,68 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package resources
import (
"time"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tolerations"
)
// CreatePodTolerations creates a list of tolerations for a pod created for the given group.
func CreatePodTolerations(mode api.DeploymentMode, group api.ServerGroup) []core.Toleration {
notReadyDur := tolerations.TolerationDuration{Forever: false, TimeSpan: time.Minute}
unreachableDur := tolerations.TolerationDuration{Forever: false, TimeSpan: time.Minute}
switch group {
case api.ServerGroupAgents:
notReadyDur.Forever = true
unreachableDur.Forever = true
case api.ServerGroupCoordinators:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
case api.ServerGroupDBServers:
notReadyDur.TimeSpan = 5 * time.Minute
unreachableDur.TimeSpan = 5 * time.Minute
case api.ServerGroupSingle:
if mode == api.DeploymentModeSingle {
notReadyDur.Forever = true
unreachableDur.Forever = true
} else {
notReadyDur.TimeSpan = 5 * time.Minute
unreachableDur.TimeSpan = 5 * time.Minute
}
case api.ServerGroupSyncMasters:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
case api.ServerGroupSyncWorkers:
notReadyDur.TimeSpan = 1 * time.Minute
unreachableDur.TimeSpan = 1 * time.Minute
case api.ServerGroupGateways:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
}
return []core.Toleration{tolerations.NewNoExecuteToleration(tolerations.TolerationKeyNodeNotReady, notReadyDur),
tolerations.NewNoExecuteToleration(tolerations.TolerationKeyNodeUnreachable, unreachableDur),
tolerations.NewNoExecuteToleration(tolerations.TolerationKeyNodeAlphaUnreachable, unreachableDur),
}
}

View file

@ -125,7 +125,7 @@ func (r *Resources) EnsureLeader(ctx context.Context, cachedStatus inspectorInte
selector := k8sutil.LabelsForLeaderMember(deploymentName, group.AsRole(), leaderID)
if s, ok := cachedStatus.Service().V1().GetSimple(leaderAgentSvcName); ok {
if c, err := patcher.ServicePatcher(ctx, cachedStatus.ServicesModInterface().V1(), s, meta.PatchOptions{}, patcher.PatchServiceSelector(selector), patcher.PatchServicePorts(ports)); err != nil {
if _, c, err := patcher.Patcher[*core.Service](ctx, cachedStatus.ServicesModInterface().V1(), s, meta.PatchOptions{}, patcher.PatchServiceSelector(selector), patcher.PatchServicePorts(ports)); err != nil {
return err
} else {
if !c {

View file

@ -131,7 +131,7 @@ func (r *Resources) EnsureSecrets(ctx context.Context, cachedStatus inspectorInt
if err := reconcileRequired.ParallelAll(len(members), func(id int) error {
switch members[id].Group.Type() {
case api.ServerGroupTypeArangoD:
case api.ServerGroupTypeArangoD, api.ServerGroupTypeGateway:
memberName := members[id].Member.ArangoMemberName(r.context.GetAPIObject().GetName(), members[id].Group)
member, ok := cachedStatus.ArangoMember().V1().GetSimple(memberName)

View file

@ -120,7 +120,7 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
continue
} else {
if changed, err := patcher.ServicePatcher(ctx, svcs, s, meta.PatchOptions{},
if _, changed, err := patcher.Patcher[*core.Service](ctx, svcs, s, meta.PatchOptions{},
patcher.PatchServicePorts(ports),
patcher.PatchServiceSelector(selector),
patcher.PatchServicePublishNotReadyAddresses(true),
@ -176,7 +176,7 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
reconcileRequired.Required()
continue
} else {
if changed, err := patcher.ServicePatcher(ctx, svcs, s, meta.PatchOptions{},
if _, changed, err := patcher.Patcher[*core.Service](ctx, svcs, s, meta.PatchOptions{},
patcher.PatchServicePorts(ports),
patcher.PatchServiceSelector(selector),
patcher.PatchServicePublishNotReadyAddresses(false),
@ -205,7 +205,7 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
log.Str("service", svcName).Debug("Created headless service")
}
} else {
if changed, err := patcher.ServicePatcher(ctx, svcs, s, meta.PatchOptions{}, patcher.PatchServicePorts(headlessPorts), patcher.PatchServiceSelector(headlessSelector)); err != nil {
if _, changed, err := patcher.Patcher[*core.Service](ctx, svcs, s, meta.PatchOptions{}, patcher.PatchServicePorts(headlessPorts), patcher.PatchServiceSelector(headlessSelector)); err != nil {
log.Err(err).Debug("Failed to patch headless service")
return errors.WithStack(err)
} else if changed {
@ -245,7 +245,7 @@ func (r *Resources) EnsureServices(ctx context.Context, cachedStatus inspectorIn
}
}
} else {
if changed, err := patcher.ServicePatcher(ctx, svcs, s, meta.PatchOptions{}, patcher.PatchServiceOnlyPorts(clientServicePorts...), patcher.PatchServiceSelector(clientServiceSelectors)); err != nil {
if _, changed, err := patcher.Patcher[*core.Service](ctx, svcs, s, meta.PatchOptions{}, patcher.PatchServiceOnlyPorts(clientServicePorts...), patcher.PatchServiceSelector(clientServiceSelectors)); err != nil {
log.Err(err).Debug("Failed to patch database client service")
return errors.WithStack(err)
} else if changed {
@ -380,7 +380,7 @@ func (r *Resources) ensureExternalAccessServices(ctx context.Context, cachedStat
}
}
if !createExternalAccessService && !deleteExternalAccessService {
if changed, err := patcher.ServicePatcher(ctx, svcs, existing, meta.PatchOptions{},
if _, changed, err := patcher.Patcher[*core.Service](ctx, svcs, existing, meta.PatchOptions{},
patcher.PatchServiceSelector(eaSelector),
patcher.Optional(patcher.PatchServiceOnlyPorts(eaPorts...), owned)); err != nil {
log.Err(err).Debug("Failed to patch database client service")
@ -434,8 +434,8 @@ func (r *Resources) ensureExternalAccessManagedServices(ctx context.Context, cac
log := r.log.Str("section", "service-ea").Str("service", eaServiceName)
managedServiceNames := spec.GetManagedServiceNames()
apply := func(svc *core.Service) (bool, error) {
return patcher.ServicePatcher(ctx, cachedStatus.ServicesModInterface().V1(), svc, meta.PatchOptions{},
apply := func(svc *core.Service) (*core.Service, bool, error) {
return patcher.Patcher[*core.Service](ctx, cachedStatus.ServicesModInterface().V1(), svc, meta.PatchOptions{},
patcher.PatchServiceSelector(selectors))
}
@ -446,7 +446,7 @@ func (r *Resources) ensureExternalAccessManagedServices(ctx context.Context, cac
log.Warn("the field \"spec.externalAccess.managedServiceNames\" should be provided for \"managed\" service type")
return nil
}
} else if changed, err := apply(svc); err != nil {
} else if _, changed, err := apply(svc); err != nil {
return errors.WithMessage(err, "failed to ensure service selector")
} else if changed {
log.Info("selector applied to the managed service \"%s\"", svc.GetName())
@ -464,7 +464,7 @@ func (r *Resources) ensureExternalAccessManagedServices(ctx context.Context, cac
continue
}
if changed, err := apply(svc); err != nil {
if _, changed, err := apply(svc); err != nil {
return errors.WithMessage(err, "failed to ensure service selector")
} else if changed {
log.Info("selector applied to the managed service \"%s\"", svcName)

View file

@ -35,7 +35,7 @@ import (
)
func (h *handler) HandleArangoDeployment(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus) (bool, error) {
var name = util.WithDefault(extension.Spec.DeploymentName)
var name = util.WithDefault(extension.Spec.Deployment)
if status.Deployment != nil {
name = status.Deployment.GetName()

View file

@ -41,7 +41,7 @@ func Test_Handler_Deployment(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -73,7 +73,7 @@ func Test_Handler_MissingDeployment(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment-missing")
obj.Spec.Deployment = util.NewType("deployment-missing")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -105,7 +105,7 @@ func Test_Handler_Deployment_Changed(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{

View file

@ -35,7 +35,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/util"
)
func (h *handler) HandleArangoDestination(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, _ *api.ArangoDeployment) (*operator.Condition, bool, error) {
func (h *handler) HandleArangoDestination(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, deployment *api.ArangoDeployment) (*operator.Condition, bool, error) {
if dest := extension.Spec.GetDestination(); dest != nil {
if svc := dest.GetService(); svc != nil {
port := svc.Port
@ -117,30 +117,56 @@ func (h *handler) HandleArangoDestination(ctx context.Context, item operation.It
}, false, nil
}
var targets = networkingApi.ArangoRouteStatusTargets{
networkingApi.ArangoRouteStatusTarget{
Url: fmt.Sprintf("%s://%s.%s.svc:%d%s", dest.GetSchema().String(), s.GetName(), s.GetNamespace(), destPort, extension.Spec.GetRoute().GetPath()),
TLS: networkingApi.ArangoRouteStatusTargetTLS{
Insecure: extension.Spec.Destination.GetTLS().GetInsecure(),
},
},
var target networkingApi.ArangoRouteStatusTarget
target.Path = dest.GetPath()
if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{
Insecure: util.NewType(extension.Spec.Destination.GetTLS().GetInsecure()),
}
}
if status.Targets.Hash() == targets.Hash() {
if ip := s.Spec.ClusterIP; ip != "" {
target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
networkingApi.ArangoRouteStatusTargetDestination{
Host: ip,
Port: destPort,
},
}
} else {
if domain := deployment.Spec.ClusterDomain; domain != nil {
target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
networkingApi.ArangoRouteStatusTargetDestination{
Host: fmt.Sprintf("%s.%s.svc.%s", s.GetName(), s.GetNamespace(), *domain),
Port: destPort,
},
}
} else {
target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
networkingApi.ArangoRouteStatusTargetDestination{
Host: fmt.Sprintf("%s.%s.svc", s.GetName(), s.GetNamespace()),
Port: destPort,
},
}
}
}
if status.Target.Hash() == target.Hash() {
return &operator.Condition{
Status: true,
Reason: "Destination Found",
Message: "Destination Found",
Hash: targets.Hash(),
Hash: target.Hash(),
}, false, nil
}
status.Targets = targets
status.Target = &target
return &operator.Condition{
Status: true,
Reason: "Destination Found",
Message: "Destination Found",
Hash: targets.Hash(),
Hash: target.Hash(),
}, true, nil
}
}
@ -154,8 +180,8 @@ func (h *handler) HandleArangoDestination(ctx context.Context, item operation.It
func (h *handler) HandleArangoDestinationWithTargets(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, depl *api.ArangoDeployment) (*operator.Condition, bool, error) {
c, changed, err := h.HandleArangoDestination(ctx, item, extension, status, depl)
if c == nil && !c.Status && status.Targets != nil {
status.Targets = nil
if c == nil && !c.Status && status.Target != nil {
status.Target = nil
changed = true
}

View file

@ -42,7 +42,7 @@ func Test_Handler_Destination_Service_Missing(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -81,7 +81,7 @@ func Test_Handler_Destination_Service_Valid(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -114,11 +114,117 @@ func Test_Handler_Destination_Service_Valid(t *testing.T) {
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
require.Len(t, extension.Status.Target.RenderURLs(), 1)
require.EqualValues(t, "http://deployment.fake.svc:10244/", extension.Status.Target.RenderURLs()[0])
c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Targets.Hash())
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
}
func Test_Handler_Destination_Service_Valid_WithIP(t *testing.T) {
// Setup
handler := newFakeHandler()
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
Service: &networkingApi.ArangoRouteSpecDestinationService{
Object: &sharedApi.Object{
Name: "deployment",
},
Port: util.NewType(intstr.FromInt32(10244)),
},
}
})
deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
obj.Spec.Ports = []core.ServicePort{
{
Port: 10244,
},
}
obj.Spec.ClusterIP = "127.0.0.2"
})
refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
// Test
require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
// Refresh
refresh(t)
// Assert
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
require.Len(t, extension.Status.Target.RenderURLs(), 1)
require.EqualValues(t, "http://127.0.0.2:10244/", extension.Status.Target.RenderURLs()[0])
c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
}
func Test_Handler_Destination_Service_Valid_WithPath(t *testing.T) {
// Setup
handler := newFakeHandler()
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
Service: &networkingApi.ArangoRouteSpecDestinationService{
Object: &sharedApi.Object{
Name: "deployment",
},
Port: util.NewType(intstr.FromInt32(10244)),
},
Path: util.NewType("/test/path/"),
}
})
deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
obj.Spec.Ports = []core.ServicePort{
{
Port: 10244,
},
}
obj.Spec.ClusterIP = "127.0.0.2"
})
refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
// Test
require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
// Refresh
refresh(t)
// Assert
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
require.Len(t, extension.Status.Target.RenderURLs(), 1)
require.EqualValues(t, "http://127.0.0.2:10244/test/path/", extension.Status.Target.RenderURLs()[0])
c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
}
func Test_Handler_Destination_Service_ValidName(t *testing.T) {
@ -128,7 +234,7 @@ func Test_Handler_Destination_Service_ValidName(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -170,7 +276,7 @@ func Test_Handler_Destination_Service_ValidName(t *testing.T) {
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Targets.Hash())
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
}
func Test_Handler_Destination_Service_WrongPort(t *testing.T) {
@ -180,7 +286,7 @@ func Test_Handler_Destination_Service_WrongPort(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -226,7 +332,7 @@ func Test_Handler_Destination_Service_WrongPortName(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -272,7 +378,7 @@ func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -309,10 +415,9 @@ func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) {
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Targets.Hash())
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
require.Len(t, extension.Status.Targets, 1)
require.False(t, extension.Status.Targets[0].TLS.Insecure)
require.False(t, extension.Status.Target.TLS.IsInsecure())
}
func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) {
@ -322,7 +427,7 @@ func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) {
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -362,20 +467,19 @@ func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) {
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Targets.Hash())
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
require.Len(t, extension.Status.Targets, 1)
require.False(t, extension.Status.Targets[0].TLS.Insecure)
require.False(t, extension.Status.Target.TLS.IsInsecure())
}
func Test_Handler_Destination_Service_Insecure_Override(t *testing.T) {
func Test_Handler_Destination_Service_Insecure_HTTPS_Override(t *testing.T) {
// Setup
handler := newFakeHandler()
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.DeploymentName = util.NewType("deployment")
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
@ -388,6 +492,7 @@ func Test_Handler_Destination_Service_Insecure_Override(t *testing.T) {
TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
Insecure: util.NewType(true),
},
Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTPS),
}
})
deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
@ -415,8 +520,60 @@ func Test_Handler_Destination_Service_Insecure_Override(t *testing.T) {
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Targets.Hash())
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
require.Len(t, extension.Status.Targets, 1)
require.True(t, extension.Status.Targets[0].TLS.Insecure)
require.True(t, extension.Status.Target.TLS.IsInsecure())
}
func Test_Handler_Destination_Service_Insecure_HTTP_Override(t *testing.T) {
// Setup
handler := newFakeHandler()
// Arrange
extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Deployment = util.NewType("deployment")
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
Service: &networkingApi.ArangoRouteSpecDestinationService{
Object: &sharedApi.Object{
Name: "deployment",
},
Port: util.NewType(intstr.FromInt32(10244)),
},
TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
Insecure: util.NewType(true),
},
Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTP),
}
})
deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
obj.Spec.Ports = []core.ServicePort{
{
Port: 10244,
},
}
})
refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
// Test
require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
// Refresh
refresh(t)
// Assert
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
require.True(t, ok)
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Reason, "Destination Found")
require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
require.False(t, extension.Status.Target.TLS.IsInsecure())
}

View file

@ -0,0 +1,59 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package sidecar
import (
"fmt"
"strings"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
type Core struct {
Internal *bool
External *bool
}
func (c *Core) GetInternal() bool {
if c == nil || c.Internal == nil {
return true
}
return *c.Internal
}
func (c *Core) GetExternal() bool {
if c == nil || c.External == nil {
return false
}
return *c.External
}
func (c *Core) Args(int Integration) k8sutil.OptionPairs {
var options k8sutil.OptionPairs
name, ver := int.Name()
options.Add(fmt.Sprintf("--integration.%s.%s.internal", strings.ToLower(name), strings.ToLower(ver)), c.GetInternal())
options.Add(fmt.Sprintf("--integration.%s.%s.external", strings.ToLower(name), strings.ToLower(ver)), c.GetExternal())
return options
}

View file

@ -0,0 +1,85 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package sidecar
import (
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
var _ IntegrationVolumes = IntegrationAuthenticationV1{}
type IntegrationAuthenticationV1 struct {
Core *Core
Deployment *api.ArangoDeployment
}
func (i IntegrationAuthenticationV1) Name() (string, string) {
return "AUTHENTICATION", "V1"
}
func (i IntegrationAuthenticationV1) Validate() error {
if i.Deployment == nil {
return errors.Errorf("Deployment is nil")
}
return nil
}
func (i IntegrationAuthenticationV1) Args() (k8sutil.OptionPairs, error) {
options := k8sutil.CreateOptionPairs()
options.Add("--integration.authentication.v1", true)
options.Add("--integration.authentication.v1.enabled", i.Deployment.GetAcceptedSpec().IsAuthenticated())
options.Add("--integration.authentication.v1.path", shared.ClusterJWTSecretVolumeMountDir)
options.Merge(i.Core.Args(i))
return options, nil
}
func (i IntegrationAuthenticationV1) Volumes() ([]core.Volume, []core.VolumeMount, error) {
if i.Deployment.GetAcceptedSpec().IsAuthenticated() {
return []core.Volume{
{
Name: shared.ClusterJWTSecretVolumeName,
VolumeSource: core.VolumeSource{
Secret: &core.SecretVolumeSource{
SecretName: pod.JWTSecretFolder(i.Deployment.GetName()),
},
},
},
}, []core.VolumeMount{
{
Name: shared.ClusterJWTSecretVolumeName,
ReadOnly: true,
MountPath: shared.ClusterJWTSecretVolumeMountDir,
},
}, nil
}
return nil, nil, nil
}

View file

@ -0,0 +1,47 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package sidecar
import (
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
type IntegrationAuthorizationV0 struct {
Core *Core
}
func (i IntegrationAuthorizationV0) Name() (string, string) {
return "AUTHORIZATION", "V0"
}
func (i IntegrationAuthorizationV0) Validate() error {
return nil
}
func (i IntegrationAuthorizationV0) Args() (k8sutil.OptionPairs, error) {
options := k8sutil.CreateOptionPairs()
options.Add("--integration.authorization.v0", true)
options.Merge(i.Core.Args(i))
return options, nil
}

View file

@ -0,0 +1,57 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package sidecar
import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
type IntegrationEnvoyV3 struct {
Core *Core
Deployment *api.ArangoDeployment
}
func (i IntegrationEnvoyV3) Name() (string, string) {
return "ENVOY", "V3"
}
func (i IntegrationEnvoyV3) Validate() error {
if i.Deployment == nil {
return errors.Errorf("Deployment is nil")
}
return nil
}
func (i IntegrationEnvoyV3) Args() (k8sutil.OptionPairs, error) {
options := k8sutil.CreateOptionPairs()
options.Add("--integration.authentication.v1", true)
options.Add("--integration.authentication.v1.enabled", i.Deployment.GetAcceptedSpec().IsAuthenticated())
options.Add("--integration.authentication.v1.path", shared.ClusterJWTSecretVolumeMountDir)
options.Merge(i.Core.Args(i))
return options, nil
}

View file

@ -0,0 +1,211 @@
//
// DISCLAIMER
//
// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany
package sidecar
import (
"fmt"
"strings"
core "k8s.io/api/core/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
schedulerContainerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container"
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container/resources"
schedulerPodApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod"
schedulerPodResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod/resources"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
const (
ContainerName = "integration"
ListenPortName = "integration"
ListenPortHealthName = "health"
)
func WithIntegrationEnvs(in Integration) ([]core.EnvVar, error) {
if v, ok := in.(IntegrationEnvs); ok {
return v.Envs()
}
return nil, nil
}
type IntegrationEnvs interface {
Integration
Envs() ([]core.EnvVar, error)
}
func WithIntegrationVolumes(in Integration) ([]core.Volume, []core.VolumeMount, error) {
if v, ok := in.(IntegrationVolumes); ok {
return v.Volumes()
}
return nil, nil, nil
}
type IntegrationVolumes interface {
Integration
Volumes() ([]core.Volume, []core.VolumeMount, error)
}
type Integration interface {
Name() (string, string)
Args() (k8sutil.OptionPairs, error)
Validate() error
}
func NewIntegration(image *schedulerContainerResourcesApi.Image, integration *schedulerApi.IntegrationSidecar, coreContainers []string, integrations ...Integration) (*schedulerApi.ProfileTemplate, error) {
for _, integration := range integrations {
if err := integration.Validate(); err != nil {
name, version := integration.Name()
return nil, errors.Wrapf(err, "Failure in %s/%s", name, version)
}
}
// Arguments
exePath := k8sutil.BinaryPath()
lifecycle, err := k8sutil.NewLifecycleFinalizersWithBinary(exePath)
if err != nil {
return nil, errors.Wrapf(err, "NewLifecycleFinalizers failed")
}
options := k8sutil.CreateOptionPairs(64)
options.Addf("--services.address", "127.0.0.1:%d", integration.GetListenPort())
options.Addf("--health.address", "0.0.0.0:%d", integration.GetControllerListenPort())
// Volumes
var volumes []core.Volume
var volumeMounts []core.VolumeMount
// Envs
var envs = []core.EnvVar{
{
Name: "INTEGRATION_API_ADDRESS",
Value: fmt.Sprintf("127.0.0.1:%d", integration.GetListenPort()),
},
{
Name: "INTEGRATION_SERVICE_ADDRESS",
Value: fmt.Sprintf("127.0.0.1:%d", integration.GetListenPort()),
},
}
for _, i := range integrations {
name, version := i.Name()
if err := i.Validate(); err != nil {
return nil, errors.Wrapf(err, "Failure in %s/%s", name, version)
}
if args, err := i.Args(); err != nil {
return nil, errors.Wrapf(err, "Failure in arguments %s/%s", name, version)
} else if len(args) > 0 {
options.Merge(args)
}
if lvolumes, lvolumeMounts, err := WithIntegrationVolumes(i); err != nil {
return nil, errors.Wrapf(err, "Failure in volumes %s/%s", name, version)
} else if len(lvolumes) > 0 || len(lvolumeMounts) > 0 {
volumes = append(volumes, lvolumes...)
volumeMounts = append(volumeMounts, lvolumeMounts...)
}
if lenvs, err := WithIntegrationEnvs(i); err != nil {
return nil, errors.Wrapf(err, "Failure in envs %s/%s", name, version)
} else if len(lenvs) > 0 {
envs = append(envs, lenvs...)
}
envs = append(envs, core.EnvVar{
Name: fmt.Sprintf("INTEGRATION_SERVICE_%s_%s", strings.ToUpper(name), strings.ToUpper(version)),
Value: fmt.Sprintf("127.0.0.1:%d", integration.GetListenPort()),
})
}
c := schedulerContainerApi.Container{
Core: &schedulerContainerResourcesApi.Core{
Command: append([]string{exePath, "integration"}, options.Sort().AsArgs()...),
},
Environments: &schedulerContainerResourcesApi.Environments{
Env: k8sutil.GetLifecycleEnv(),
},
Networking: &schedulerContainerResourcesApi.Networking{
Ports: []core.ContainerPort{
{
Name: ListenPortName,
ContainerPort: int32(integration.GetListenPort()),
Protocol: core.ProtocolTCP,
},
{
Name: ListenPortHealthName,
ContainerPort: int32(integration.GetControllerListenPort()),
Protocol: core.ProtocolTCP,
},
},
},
Image: image,
Lifecycle: &schedulerContainerResourcesApi.Lifecycle{
Lifecycle: lifecycle,
},
Probes: &schedulerContainerResourcesApi.Probes{
ReadinessProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
GRPC: &core.GRPCAction{
Port: int32(integration.GetControllerListenPort()),
},
},
InitialDelaySeconds: 1, // Wait 1s before first probe
TimeoutSeconds: 2, // Timeout of each probe is 2s
PeriodSeconds: 30, // Interval between probes is 30s
SuccessThreshold: 1, // Single probe is enough to indicate success
FailureThreshold: 2, // Need 2 failed probes to consider a failed state
},
},
VolumeMounts: &schedulerContainerResourcesApi.VolumeMounts{
VolumeMounts: volumeMounts,
},
}
pt := schedulerApi.ProfileTemplate{
Container: &schedulerApi.ProfileContainerTemplate{
Containers: map[string]schedulerContainerApi.Container{
ContainerName: util.TypeOrDefault(k8sutil.CreateDefaultContainerTemplate(image).With(&c).With(integration.GetContainer())),
},
},
Pod: &schedulerPodApi.Pod{
Metadata: &schedulerPodResourcesApi.Metadata{
Annotations: map[string]string{},
},
Volumes: &schedulerPodResourcesApi.Volumes{
Volumes: volumes,
},
},
}
for _, container := range coreContainers {
pt.Pod.Metadata.Annotations[fmt.Sprintf("%s/%s", constants.AnnotationShutdownCoreContainer, container)] = constants.AnnotationShutdownCoreContainerModeWait
}
pt.Pod.Metadata.Annotations[fmt.Sprintf("%s/%s", constants.AnnotationShutdownContainer, ContainerName)] = ListenPortHealthName
pt.Pod.Metadata.Annotations[constants.AnnotationShutdownManagedContainer] = "true"
pt.Container.Containers.ExtendContainers(&schedulerContainerApi.Container{
Environments: &schedulerContainerResourcesApi.Environments{
Env: envs,
},
}, coreContainers...)
return &pt, nil
}

View file

@ -0,0 +1,47 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package sidecar
import (
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
type IntegrationShutdownV1 struct {
Core *Core
}
func (i IntegrationShutdownV1) Name() (string, string) {
return "SHUTDOWN", "V1"
}
func (i IntegrationShutdownV1) Validate() error {
return nil
}
func (i IntegrationShutdownV1) Args() (k8sutil.OptionPairs, error) {
options := k8sutil.CreateOptionPairs()
options.Add("--integration.shutdown.v1", true)
options.Merge(i.Core.Args(i))
return options, nil
}

50
pkg/util/dict_test.go Normal file
View file

@ -0,0 +1,50 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package util
import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
func testDictDefaultValue[T any](t *testing.T, expected T) {
t.Run(reflect.TypeOf(expected).String(), func(t *testing.T) {
m := map[string]T{}
ev, ok := m["missing"]
require.False(t, ok)
require.Equal(t, expected, ev)
evs := m["missing"]
require.Equal(t, expected, evs)
})
}
func Test_Dict_Types(t *testing.T) {
testDictDefaultValue[string](t, "")
testDictDefaultValue[int](t, 0)
testDictDefaultValue[*string](t, nil)
testDictDefaultValue[*int](t, nil)
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@ import (
core "k8s.io/api/core/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/service"
)
@ -39,11 +40,11 @@ type PodModifier interface {
}
type PodCreator interface {
Init(context.Context, Inspector, *core.Pod) error
Init(context.Context, Inspector, *core.PodTemplateSpec) error
GetName() string
GetRole() string
GetVolumes() []core.Volume
GetSidecars(*core.Pod) error
GetSidecars(*core.PodTemplateSpec) error
GetInitContainers(cachedStatus Inspector) ([]core.Container, error)
GetFinalizers() []string
GetTolerations() []core.Toleration
@ -61,6 +62,8 @@ type PodCreator interface {
Annotations() map[string]string
Labels() map[string]string
Profiles() (schedulerApi.ProfileTemplates, error)
PodModifier
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -148,6 +148,17 @@ func (o OptionPairs) AsArgs() []string {
return s
}
func (o OptionPairs) AsSplittedArgs() []string {
s := make([]string, len(o)*2)
for id, pair := range o {
s[id*2] = pair.Key
s[id*2+1] = pair.Value
}
return s
}
// OptionPair key value pair builder
type OptionPair struct {
Key string

View file

@ -0,0 +1,40 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package patcher
import (
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
)
func PatchConfigMapData(data map[string]string) Patch[*core.ConfigMap] {
return func(in *core.ConfigMap) []patch.Item {
if len(data) == len(in.Data) && equality.Semantic.DeepDerivative(data, in.Data) {
return nil
}
return []patch.Item{
patch.ItemReplace(patch.NewPath("data"), data),
}
}
}

View file

@ -0,0 +1,105 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package patcher
import (
"context"
"testing"
"github.com/stretchr/testify/require"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/util/tests"
)
func Test_ConfigMap(t *testing.T) {
c := tests.NewEmptyInspector(t)
t.Run("Create", func(t *testing.T) {
require.NoError(t, c.Refresh(context.Background()))
_, err := c.ConfigMapsModInterface().V1().Create(context.Background(), &core.ConfigMap{
ObjectMeta: meta.ObjectMeta{
Name: "test",
Namespace: c.Namespace(),
},
}, meta.CreateOptions{})
require.NoError(t, err)
})
require.NoError(t, c.Refresh(context.Background()))
t.Run("Check", func(t *testing.T) {
cm, ok := c.ConfigMap().V1().GetSimple("test")
require.True(t, ok)
require.Len(t, cm.Data, 0)
})
require.NoError(t, c.Refresh(context.Background()))
t.Run("Update", func(t *testing.T) {
cm, ok := c.ConfigMap().V1().GetSimple("test")
require.True(t, ok)
uCm, ok, err := Patcher[*core.ConfigMap](context.Background(), c.ConfigMapsModInterface().V1(), cm, meta.PatchOptions{}, PatchConfigMapData(map[string]string{
"A": "B",
}))
require.NoError(t, err)
require.True(t, ok)
require.NoError(t, c.Refresh(context.Background()))
cm, ok = c.ConfigMap().V1().GetSimple("test")
require.True(t, ok)
require.Equal(t, map[string]string{
"A": "B",
}, uCm.Data)
require.Equal(t, map[string]string{
"A": "B",
}, cm.Data)
})
t.Run("Reupdate", func(t *testing.T) {
cm, ok := c.ConfigMap().V1().GetSimple("test")
require.True(t, ok)
uCm, ok, err := Patcher[*core.ConfigMap](context.Background(), c.ConfigMapsModInterface().V1(), cm, meta.PatchOptions{}, PatchConfigMapData(map[string]string{
"A": "B",
}))
require.NoError(t, err)
require.False(t, ok)
require.NoError(t, c.Refresh(context.Background()))
cm, ok = c.ConfigMap().V1().GetSimple("test")
require.True(t, ok)
require.Equal(t, map[string]string{
"A": "B",
}, uCm.Data)
require.Equal(t, map[string]string{
"A": "B",
}, cm.Data)
})
}

View file

@ -0,0 +1,89 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package patcher
import (
"context"
"reflect"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
)
type Patch[T meta.Object] func(in T) []patch.Item
type Client[T meta.Object] interface {
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts meta.PatchOptions, subresources ...string) (result T, err error)
}
func Patcher[T meta.Object](ctx context.Context, client Client[T], in T, opts meta.PatchOptions, functions ...Patch[T]) (T, bool, error) {
if v := reflect.ValueOf(in); !v.IsValid() || v.IsZero() {
return util.Default[T](), false, nil
}
if in.GetName() == "" {
return util.Default[T](), false, nil
}
var items []patch.Item
for id := range functions {
if f := functions[id]; f != nil {
items = append(items, f(in)...)
}
}
if len(items) == 0 {
return in, false, nil
}
data, err := patch.NewPatch(items...).Marshal()
if err != nil {
return util.Default[T](), false, err
}
nctx, c := globals.GetGlobals().Timeouts().Kubernetes().WithTimeout(ctx)
defer c()
if obj, err := client.Patch(nctx, in.GetName(), types.JSONPatchType, data, opts); err != nil {
return util.Default[T](), false, err
} else {
return obj, true, nil
}
}
func Optional[T meta.Object](p Patch[T], enabled bool) Patch[T] {
return func(in T) []patch.Item {
if !enabled {
return nil
}
if p != nil {
return p(in)
}
return nil
}
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -21,57 +21,13 @@
package patcher
import (
"context"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
v1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/service/v1"
)
type ServicePatch func(in *core.Service) []patch.Item
func ServicePatcher(ctx context.Context, client v1.ModInterface, in *core.Service, opts meta.PatchOptions, functions ...ServicePatch) (bool, error) {
if in == nil {
return false, nil
}
if in.GetName() == "" {
return false, nil
}
var items []patch.Item
for id := range functions {
if f := functions[id]; f != nil {
items = append(items, f(in)...)
}
}
if len(items) == 0 {
return false, nil
}
data, err := patch.NewPatch(items...).Marshal()
if err != nil {
return false, err
}
nctx, c := globals.GetGlobals().Timeouts().Kubernetes().WithTimeout(ctx)
defer c()
if _, err := client.Patch(nctx, in.GetName(), types.JSONPatchType, data, opts); err != nil {
return false, err
}
return true, nil
}
func PatchServicePorts(ports []core.ServicePort) ServicePatch {
func PatchServicePorts(ports []core.ServicePort) Patch[*core.Service] {
return func(in *core.Service) []patch.Item {
if len(ports) == len(in.Spec.Ports) && equality.Semantic.DeepDerivative(ports, in.Spec.Ports) {
return nil
@ -83,21 +39,7 @@ func PatchServicePorts(ports []core.ServicePort) ServicePatch {
}
}
func Optional(p ServicePatch, enabled bool) ServicePatch {
return func(in *core.Service) []patch.Item {
if !enabled {
return nil
}
if p != nil {
return p(in)
}
return nil
}
}
func PatchServiceOnlyPorts(ports ...core.ServicePort) ServicePatch {
func PatchServiceOnlyPorts(ports ...core.ServicePort) Patch[*core.Service] {
return func(in *core.Service) []patch.Item {
psvc := in.Spec.DeepCopy()
cp := psvc.Ports
@ -149,7 +91,7 @@ func PatchServiceOnlyPorts(ports ...core.ServicePort) ServicePatch {
}
}
func PatchServiceSelector(selector map[string]string) ServicePatch {
func PatchServiceSelector(selector map[string]string) Patch[*core.Service] {
return func(in *core.Service) []patch.Item {
if in.Spec.Selector != nil && equality.Semantic.DeepEqual(in.Spec.Selector, selector) {
return nil
@ -161,7 +103,7 @@ func PatchServiceSelector(selector map[string]string) ServicePatch {
}
}
func PatchServiceType(t core.ServiceType) ServicePatch {
func PatchServiceType(t core.ServiceType) Patch[*core.Service] {
return func(in *core.Service) []patch.Item {
if in.Spec.Type == t {
return nil
@ -173,7 +115,7 @@ func PatchServiceType(t core.ServiceType) ServicePatch {
}
}
func PatchServicePublishNotReadyAddresses(publishNotReadyAddresses bool) ServicePatch {
func PatchServicePublishNotReadyAddresses(publishNotReadyAddresses bool) Patch[*core.Service] {
return func(in *core.Service) []patch.Item {
if in.Spec.PublishNotReadyAddresses == publishNotReadyAddresses {
return nil

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -53,7 +53,7 @@ func Test_Service(t *testing.T) {
require.True(t, ok)
require.False(t, svc.Spec.PublishNotReadyAddresses)
changed, err := ServicePatcher(context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(true))
_, changed, err := Patcher[*core.Service](context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(true))
require.NoError(t, err)
require.True(t, changed)
@ -69,7 +69,7 @@ func Test_Service(t *testing.T) {
require.True(t, ok)
require.True(t, svc.Spec.PublishNotReadyAddresses)
changed, err := ServicePatcher(context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(true))
_, changed, err := Patcher[*core.Service](context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(true))
require.NoError(t, err)
require.False(t, changed)
@ -85,7 +85,7 @@ func Test_Service(t *testing.T) {
require.True(t, ok)
require.True(t, svc.Spec.PublishNotReadyAddresses)
changed, err := ServicePatcher(context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(false))
_, changed, err := Patcher[*core.Service](context.Background(), c.ServicesModInterface().V1(), svc, meta.PatchOptions{}, PatchServicePublishNotReadyAddresses(false))
require.NoError(t, err)
require.True(t, changed)

View file

@ -560,10 +560,10 @@ func NewContainer(containerCreator interfaces.ContainerCreator) (core.Container,
}
// NewPod creates a basic Pod for given settings.
func NewPod(deploymentName, role, id, podName string, podCreator interfaces.PodCreator) core.Pod {
func NewPod(deploymentName, role, id, podName string, podCreator interfaces.PodCreator) core.PodTemplateSpec {
hostname := shared.CreatePodHostName(deploymentName, role, id)
p := core.Pod{
p := core.PodTemplateSpec{
ObjectMeta: meta.ObjectMeta{
Name: podName,
Labels: LabelsForMember(deploymentName, role, id),

View file

@ -82,7 +82,7 @@ func CreateExporterService(ctx context.Context, cachedStatus inspector.Inspector
svcName := CreateExporterClientServiceName(deploymentName)
if svc, exists := cachedStatus.Service().V1().GetSimple(svcName); exists {
if changed, err := patcher.ServicePatcher(ctx, cachedStatus.ServicesModInterface().V1(), svc, meta.PatchOptions{}, patcher.PatchServiceSelector(selectors), patcher.PatchServicePorts(ports)); err != nil {
if _, changed, err := patcher.Patcher[*core.Service](ctx, cachedStatus.ServicesModInterface().V1(), svc, meta.PatchOptions{}, patcher.PatchServiceSelector(selectors), patcher.PatchServicePorts(ports)); err != nil {
return "", false, err
} else {
return svcName, changed, nil

View file

@ -24,8 +24,6 @@ import (
"time"
core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
const (
@ -105,41 +103,3 @@ func AddTolerationIfNotFound(source []core.Toleration, toAdd core.Toleration) []
return append(source, toAdd)
}
// CreatePodTolerations creates a list of tolerations for a pod created for the given group.
func CreatePodTolerations(mode api.DeploymentMode, group api.ServerGroup) []core.Toleration {
notReadyDur := TolerationDuration{Forever: false, TimeSpan: time.Minute}
unreachableDur := TolerationDuration{Forever: false, TimeSpan: time.Minute}
switch group {
case api.ServerGroupAgents:
notReadyDur.Forever = true
unreachableDur.Forever = true
case api.ServerGroupCoordinators:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
case api.ServerGroupDBServers:
notReadyDur.TimeSpan = 5 * time.Minute
unreachableDur.TimeSpan = 5 * time.Minute
case api.ServerGroupSingle:
if mode == api.DeploymentModeSingle {
notReadyDur.Forever = true
unreachableDur.Forever = true
} else {
notReadyDur.TimeSpan = 5 * time.Minute
unreachableDur.TimeSpan = 5 * time.Minute
}
case api.ServerGroupSyncMasters:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
case api.ServerGroupSyncWorkers:
notReadyDur.TimeSpan = 1 * time.Minute
unreachableDur.TimeSpan = 1 * time.Minute
case api.ServerGroupGateways:
notReadyDur.TimeSpan = 15 * time.Second
unreachableDur.TimeSpan = 15 * time.Second
}
return []core.Toleration{NewNoExecuteToleration(TolerationKeyNodeNotReady, notReadyDur),
NewNoExecuteToleration(TolerationKeyNodeUnreachable, unreachableDur),
NewNoExecuteToleration(TolerationKeyNodeAlphaUnreachable, unreachableDur),
}
}