├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build ├── Dockerfile └── bin │ ├── entrypoint │ └── user_setup ├── cmd └── manager │ └── main.go ├── deploy ├── crds │ ├── prestodb.io_prestos_crd.yaml │ └── prestodb.yaml ├── metrics_server.yaml └── operator.yaml ├── docker ├── Dockerfile ├── gcloudDockerBuild.sh └── prestooperator.yaml ├── docs ├── additionalvolumes.md ├── autoscaling.md ├── catalog.md ├── caveats.md ├── https.md ├── prestoresource.md ├── service.md └── status.md ├── go.mod ├── go.sum ├── nohup.out ├── pkg ├── apis │ ├── addtoscheme_prestodb_v1alpha1.go │ ├── apis.go │ └── prestodb │ │ ├── group.go │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── presto_types.go │ │ ├── presto_webhook.go │ │ ├── register.go │ │ ├── zz_generated.deepcopy.go │ │ └── zz_generated.openapi.go └── controller │ ├── add_presto.go │ ├── controller.go │ └── presto │ ├── catalog.go │ ├── common_util.go │ ├── constants.go │ ├── errors.go │ ├── hpa_handler.go │ ├── presto_controller.go │ ├── presto_pods.go │ ├── presto_properties.go │ ├── services.go │ └── watch_predicate.go ├── tools.go └── version └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | This project is maintained by the individuals below, who have committer rights to this 4 | repository and are the primary maintainers in addition to PrestoDB committers: 5 | 6 | * [Hemant Bhanawat](https://github.com/hbhanawat) 7 | * [Suranjan Kumar](https://github.com/suranjan) 8 | * [Neeraj Kumar](https://github.com/kneeraj) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Presto Kubernetes Operator 2 | 3 | [Kubernetes](https://kubernetes.io/docs/home/) is an open source container orchestration engine for automating deployment, scaling, and management of containerized applications. [Operators](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) are software extensions to Kubernetes that make use of custom resources to manage applications and their components. Operators follow Kubernetes principles, notably the control loop. 4 | 5 | Use this operator to manage Presto clusters which are deployed as custom resources. In short, the task of configuring, creating, managing, automatically scaling up and scaling-in of Presto cluster(s) in a Kubernetes environment has been made simple, easy and quick. 6 | 7 | ## Deploying Operator 8 | 9 | ### Deploying Operator - Locally 10 | *Step 1:* Enable metrics server for k8s, if not already enabled. [See this](https://github.com/kubernetes-sigs/metrics-server). This is needed for horizontal pod autoscaling. 11 | 12 | *Step 2:* Build the operator 13 | ```bash 14 | $ go build -o presto-kubernetes-operator cmd/manager/main.go 15 | ``` 16 | 17 | *Step 3:* Deploy the CRD 18 | ```bash 19 | $ kubectl apply -f deploy/crds/prestodb.io_prestos_crd.yaml 20 | ``` 21 | 22 | *Step 4:* Start the controller with the right credentials 23 | ```bash 24 | $ ./presto-kubernetes-operator -kubeconfig /home/hemant/.kube/config 25 | ``` 26 | 27 | ### Deploying Operator - GKE 28 | 29 | *Step 1:* Enable metrics server for GKE, if not already enabled. [See this](https://github.com/kubernetes-sigs/metrics-server). This is needed for horizontal pod autoscaling. 30 | 31 | *Step 2:* Create Operator Image Using Google CloudBuild 32 | ```bash 33 | $ docker/gcloudDockerBuild.sh 34 | ``` 35 | *Step 3:* Deploy the CRD 36 | ```bash 37 | $ kubectl apply -f deploy/crds/prestodb.io_prestos_crd.yaml 38 | ``` 39 | *Step 4:* Update the Operator yaml with image name 40 | ```bash 41 | # Here gcr.io/fluid-tangent-249303/presto-kubernetes-operator is the name of image. 42 | # Replace it with your image name 43 | $ sed -i 's/REPLACE_IMAGE/gcr.io\/fluid-tangent-249303\/presto-kubernetes-operator/g' deploy/operator.yaml 44 | ``` 45 | 46 | *Step 5:* Launch the operator 47 | ```bash 48 | $ kubectl apply -f deploy/operator.yaml 49 | ``` 50 | 51 | ## Deploy Presto Cluster 52 | 53 | Deploy the presto cluster 54 | ```bash 55 | $ ## Deploy Presto 56 | $ kubectl apply -f deploy/crds/prestodb.yaml 57 | ``` 58 | 59 | ## Further Details 60 | 61 | - [Creating Presto Cluster](docs/prestoresource.md) 62 | - [Managing Presto Cluster](docs/status.md) 63 | - [Autoscaling](docs/autoscaling.md) 64 | - [Catalogs](docs/catalog.md) 65 | - [Services](docs/service.md) 66 | - [Additional Volumes](docs/additionalvolumes.md) 67 | - [HTTPS Support](docs/https.md) 68 | - [Caveats/Future Work](docs/caveats.md) 69 | 70 | ## Community support 71 | 72 | [Slack](https://join.slack.com/t/prestodb/shared_invite/enQtNTQ3NjU2MTYyNDA2LTYyOTg3MzUyMWE1YTI3Njc5YjgxZjNiYTgxODAzYjI5YWMwYWE0MTZjYWFhNGMwNjczYjI3N2JhM2ExMGJlMWM) ![Slack](http://i.imgur.com/h3sc6GM.png) 73 | 74 | -------------------------------------------------------------------------------- /build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest 2 | 3 | ENV OPERATOR=/usr/local/bin/presto-kubernetes-operator \ 4 | USER_UID=1001 \ 5 | USER_NAME=presto-kubernetes-operator 6 | 7 | # install operator binary 8 | COPY build/_output/bin/presto-kubernetes-operator ${OPERATOR} 9 | 10 | COPY build/bin /usr/local/bin 11 | RUN /usr/local/bin/user_setup 12 | 13 | ENTRYPOINT ["/usr/local/bin/entrypoint"] 14 | 15 | USER ${USER_UID} 16 | -------------------------------------------------------------------------------- /build/bin/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # This is documented here: 4 | # https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines 5 | 6 | if ! whoami &>/dev/null; then 7 | if [ -w /etc/passwd ]; then 8 | echo "${USER_NAME:-presto-kubernetes-operator}:x:$(id -u):$(id -g):${USER_NAME:-presto-kubernetes-operator} user:${HOME}:/sbin/nologin" >> /etc/passwd 9 | fi 10 | fi 11 | 12 | exec ${OPERATOR} $@ 13 | -------------------------------------------------------------------------------- /build/bin/user_setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | # ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) 5 | mkdir -p ${HOME} 6 | chown ${USER_UID}:0 ${HOME} 7 | chmod ug+rwx ${HOME} 8 | 9 | # runtime user will need to be able to self-insert in /etc/passwd 10 | chmod g+rw /etc/passwd 11 | 12 | # no need for this script to remain in the image after running 13 | rm $0 14 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis" 7 | "github.com/prestodb/presto-kubernetes-operator/pkg/controller" 8 | "github.com/prestodb/presto-kubernetes-operator/pkg/controller/presto" 9 | apimachineryruntime "k8s.io/apimachinery/pkg/runtime" 10 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 11 | metrics "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 12 | "os" 13 | "runtime" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 16 | // +kubebuilder:scaffold:imports 17 | ) 18 | 19 | var ( 20 | scheme = apimachineryruntime.NewScheme() 21 | setupLog = ctrl.Log.WithName("setup") 22 | ) 23 | 24 | func printVersion() { 25 | setupLog.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) 26 | setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) 27 | } 28 | func main() { 29 | var cmdLineParams = presto.CommandLineParams{} 30 | flag.IntVar(&cmdLineParams.StatusUpdateInterval, 31 | "status-update-interval", 10, "Presto status update interval.") 32 | 33 | flag.Parse() 34 | printVersion() 35 | 36 | ctrl.SetLogger(zap.New(func(o *zap.Options) { 37 | o.Development = true 38 | })) 39 | 40 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) 41 | if err != nil { 42 | setupLog.Error(err, "unable to start manager") 43 | os.Exit(1) 44 | } 45 | 46 | metricsClient, err := metrics.NewForConfig(ctrl.GetConfigOrDie()) 47 | if err != nil { 48 | setupLog.Error(err, "Could not create metrics client") 49 | os.Exit(1) 50 | } 51 | 52 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 53 | setupLog.Error(err, "") 54 | os.Exit(1) 55 | } 56 | 57 | if err = controller.AddToManager( 58 | mgr, metricsClient, cmdLineParams, 59 | ctrl.Log.WithName("controllers").WithName("Deployment")); err != nil { 60 | setupLog.Error(err, "unable to create controller", "controller", "Deployment") 61 | os.Exit(1) 62 | } 63 | 64 | //if err = (&v1alpha1.Presto{}).SetupWebhookWithManager(mgr); err != nil { 65 | // setupLog.Error(err, "unable to create webhook", "webhook", "Presto") 66 | // os.Exit(1) 67 | //} 68 | 69 | setupLog.Info("starting manager") 70 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 71 | setupLog.Error(err, "problem running manager") 72 | os.Exit(1) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /deploy/crds/prestodb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: prestodb.io/v1alpha1 2 | kind: Presto 3 | metadata: 4 | name: mycluster 5 | spec: 6 | service: 7 | type: "LoadBalancer" 8 | port: 8100 9 | catalogs: 10 | catalogSpec: 11 | - name: hive 12 | content: 13 | connector.name: hive-hadoop2 14 | hive.metastore.uri: thrift://35.192.90.145:9083 15 | hive.metastore.authentication.type: NONE 16 | hive.gcs.json-key-file-path: /etc/gcssecret/integratedtesting-e278ad542088.json 17 | - name: postgres 18 | content: 19 | connector.name: postgresql 20 | connection-url: jdbc:postgresql://35.192.90.145:5432/testdb 21 | connection-user: prestogateway 22 | connection-password: root123 23 | # kubectl create secret generic tpchsecret --from-literal=mytpch='connector.name=tpch' --from-literal=myjmx='connector.name=jmx' 24 | # catalogSecrets: 25 | # - secretName: tpchsecret 26 | # secretKey: mytpch 27 | # - secretName: tpchsecret 28 | # secretKey: myjmx 29 | volumes: 30 | - name: spillvol 31 | emptyDir: {} 32 | mountPath: /prestospill 33 | # - name: gcssecret 34 | # secret: 35 | # secretName: gcssecret 36 | # mountPath: "/etc/gcssecret" 37 | # - name: resourcejson1 38 | # secret: 39 | # secretName: resourcejson1 40 | # mountPath: "/etc/resourcejson1" 41 | imageDetails: 42 | name: "trivadis/prestodb:latest" 43 | prestoPath: "/opt/presto/etc" 44 | coordinator: 45 | memoryLimit: "1Gi" 46 | cpuLimit: "0.25" 47 | # kubectl create secret generic prestokeystore --from-file=/tmp/etc/prestoserverkeystore.jks 48 | httpsEnabled: false 49 | httpsKeyPairSecretName: "prestokeystore" 50 | httpsKeyPairSecretKey: "prestoserverkeystore.jks" 51 | httpsKeyPairPassword: "password" 52 | worker: 53 | memoryLimit: "1Gi" 54 | cpuLimit: "0.25" 55 | count: 1 56 | autoscaling: 57 | enabled: true 58 | minReplicas: 1 59 | maxReplicas: 4 60 | targetCPUUtilizationPercentage: 30 61 | additionalProps: 62 | shutdown.grace-period: 10s 63 | # additionalPrestoPropFiles: 64 | # resource-groups.properties: 65 | # "resource-groups.configuration-manager=file\n 66 | # resource-groups.config-file=/etc/resourcejson1/resource-groups.json" 67 | 68 | -------------------------------------------------------------------------------- /deploy/metrics_server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: metrics-server 5 | namespace: kube-system 6 | labels: 7 | kubernetes.io/cluster-service: "true" 8 | addonmanager.kubernetes.io/mode: EnsureExists 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: system:metrics-server 14 | labels: 15 | kubernetes.io/cluster-service: "true" 16 | addonmanager.kubernetes.io/mode: EnsureExists 17 | rules: 18 | - apiGroups: 19 | - "" 20 | resources: 21 | - pods 22 | - nodes 23 | - namespaces 24 | verbs: 25 | - get 26 | - list 27 | - watch 28 | - apiGroups: 29 | - "extensions" 30 | resources: 31 | - deployments 32 | verbs: 33 | - get 34 | - list 35 | - watch 36 | --- 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | kind: ClusterRoleBinding 39 | metadata: 40 | name: system:metrics-server 41 | labels: 42 | kubernetes.io/cluster-service: "true" 43 | addonmanager.kubernetes.io/mode: EnsureExists 44 | roleRef: 45 | apiGroup: rbac.authorization.k8s.io 46 | kind: ClusterRole 47 | name: system:metrics-server 48 | subjects: 49 | - kind: ServiceAccount 50 | name: metrics-server 51 | namespace: kube-system 52 | --- 53 | apiVersion: rbac.authorization.k8s.io/v1beta1 54 | kind: RoleBinding 55 | metadata: 56 | name: metrics-server-auth-reader 57 | namespace: kube-system 58 | labels: 59 | kubernetes.io/cluster-service: "true" 60 | addonmanager.kubernetes.io/mode: EnsureExists 61 | roleRef: 62 | apiGroup: rbac.authorization.k8s.io 63 | kind: Role 64 | name: extension-apiserver-authentication-reader 65 | subjects: 66 | - kind: ServiceAccount 67 | name: metrics-server 68 | namespace: kube-system 69 | --- 70 | apiVersion: rbac.authorization.k8s.io/v1beta1 71 | kind: ClusterRoleBinding 72 | metadata: 73 | name: metrics-server:system:auth-delegator 74 | labels: 75 | kubernetes.io/cluster-service: "true" 76 | addonmanager.kubernetes.io/mode: EnsureExists 77 | roleRef: 78 | apiGroup: rbac.authorization.k8s.io 79 | kind: ClusterRole 80 | name: system:auth-delegator 81 | subjects: 82 | - kind: ServiceAccount 83 | name: metrics-server 84 | namespace: kube-system 85 | --- 86 | apiVersion: v1 87 | kind: Service 88 | metadata: 89 | name: metrics-server 90 | namespace: kube-system 91 | labels: 92 | addonmanager.kubernetes.io/mode: EnsureExists 93 | kubernetes.io/name: "Metrics-server" 94 | kubernetes.io/cluster-service: "true" 95 | spec: 96 | selector: 97 | k8s-app: metrics-server 98 | ports: 99 | - port: 443 100 | protocol: TCP 101 | targetPort: 443 102 | --- 103 | apiVersion: extensions/v1beta1 104 | kind: Deployment 105 | metadata: 106 | name: metrics-server 107 | namespace: kube-system 108 | labels: 109 | k8s-app: metrics-server 110 | kubernetes.io/cluster-service: "true" 111 | addonmanager.kubernetes.io/mode: Reconcile 112 | spec: 113 | selector: 114 | matchLabels: 115 | k8s-app: metrics-server 116 | template: 117 | metadata: 118 | name: metrics-server 119 | labels: 120 | k8s-app: metrics-server 121 | spec: 122 | serviceAccountName: metrics-server 123 | containers: 124 | - name: metrics-server 125 | image: "k8s.gcr.io/metrics-server-amd64:v0.3.4" 126 | imagePullPolicy: IfNotPresent 127 | command: 128 | - /metrics-server 129 | # These are needed for GKE, which doesn't support secure communication yet. 130 | # Remove these lines for non-GKE clusters, and when GKE supports token-based auth. 131 | - --kubelet-port=10255 132 | - --deprecated-kubelet-completely-insecure=true 133 | - --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP 134 | nodeSelector: 135 | beta.kubernetes.io/os: linux 136 | --- 137 | apiVersion: apiregistration.k8s.io/v1beta1 138 | kind: APIService 139 | metadata: 140 | name: v1beta1.metrics.k8s.io 141 | labels: 142 | kubernetes.io/cluster-service: "true" 143 | addonmanager.kubernetes.io/mode: EnsureExists 144 | spec: 145 | service: 146 | name: metrics-server 147 | namespace: kube-system 148 | group: metrics.k8s.io 149 | version: v1beta1 150 | insecureSkipTLSVerify: true 151 | groupPriorityMinimum: 100 152 | versionPriority: 100 153 | -------------------------------------------------------------------------------- /deploy/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: presto-kubernetes-operator 5 | namespace: default 6 | 7 | --- 8 | kind: ClusterRole 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | metadata: 11 | name: presto-kubernetes-operator 12 | rules: 13 | - apiGroups: [""] # "" indicates the core API group 14 | resources: ["pods", "services", "events", "services/finalizers", "endpoints", "persistentvolumeclaims", "configmaps", "secrets"] 15 | verbs: ["*"] 16 | - apiGroups: ["apps"] 17 | resources: ["replicasets"] 18 | verbs: ["*"] 19 | - apiGroups: ["prestodb.io"] 20 | resources: ["prestos", "prestos/status"] 21 | verbs: ["*"] 22 | - apiGroups: ["autoscaling"] 23 | resources: ["horizontalpodautoscalers"] 24 | verbs: ["*"] 25 | - apiGroups: ["metrics.k8s.io"] 26 | resources: ["pods", "nodes"] 27 | verbs: ["*"] 28 | 29 | --- 30 | kind: ClusterRoleBinding 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | metadata: 33 | name: presto-kubernetes-operator 34 | namespace: default 35 | subjects: 36 | - kind: ServiceAccount 37 | namespace: default 38 | name: presto-kubernetes-operator 39 | roleRef: 40 | kind: ClusterRole 41 | name: presto-kubernetes-operator 42 | apiGroup: rbac.authorization.k8s.io 43 | 44 | --- 45 | apiVersion: apps/v1 46 | kind: Deployment 47 | metadata: 48 | name: presto-kubernetes-operator 49 | namespace: default 50 | spec: 51 | replicas: 1 52 | selector: 53 | matchLabels: 54 | name: presto-kubernetes-operator 55 | template: 56 | metadata: 57 | labels: 58 | name: presto-kubernetes-operator 59 | spec: 60 | serviceAccountName: presto-kubernetes-operator 61 | containers: 62 | - name: presto-kubernetes-operator 63 | # Replace this with the built image name 64 | image: prestodb/presto-kubernetes-operator:0.1 65 | command: [ "/bin/presto-kubernetes-operator" ] 66 | imagePullPolicy: Always 67 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13-alpine AS build 2 | 3 | 4 | RUN apk add --no-cache git 5 | COPY go.mod /go/src/github.com/prestodb/presto-kubernetes-operator/ 6 | WORKDIR /go/src/github.com/prestodb/presto-kubernetes-operator/ 7 | RUN go mod download 8 | 9 | COPY . /go/src/github.com/prestodb/presto-kubernetes-operator/ 10 | RUN go build -o /bin/presto-kubernetes-operator cmd/manager/main.go 11 | 12 | FROM alpine:3.7 13 | COPY --from=build /bin/presto-kubernetes-operator /bin/presto-kubernetes-operator 14 | ENTRYPOINT ["/bin/presto-kubernetes-operator"] 15 | -------------------------------------------------------------------------------- /docker/gcloudDockerBuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 3 | gcloud builds submit --config $SCRIPTPATH/prestooperator.yaml $SCRIPTPATH/.. 4 | -------------------------------------------------------------------------------- /docker/prestooperator.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'gcr.io/cloud-builders/docker' 3 | args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/presto-operator', '-f', 'docker/Dockerfile', '.'] 4 | images: 5 | - 'gcr.io/$PROJECT_ID/presto-operator' 6 | -------------------------------------------------------------------------------- /docs/additionalvolumes.md: -------------------------------------------------------------------------------- 1 | # Additional Volumes 2 | Additional volumes may be needed for things like adding spill directories for the Presto cluster or for maintaining the data directory on a long term storage. For such a use case, additional volumes can be specified for Presto Cluster just like how they are specified with pods. For non-persistent volumes, the lifecycle of volumes would be tied with the lifecycle of the Presto Cluster. The specified volumes are mounted on the Presto server and Presto workers. 3 | 4 | For specifying the volume, the mount point and the volumes can be specified together. For e.g. 5 | 6 | ```bash 7 | # An empty dir (Kubernetes construct) is specified 8 | # as the volume and is mounted on /prestospill 9 | spec: 10 | volumes: 11 | - name: spillvol 12 | emptyDir: {} 13 | mountPath: /prestospill 14 | # /prestospill folder is used as the spill folder 15 | coordinator: 16 | additionalProps: 17 | spill-enabled: "true" 18 | spiller-spill-path: "/prestospill" 19 | coordinator: 20 | additionalProps: 21 | spill-enabled: "true" 22 | spiller-spill-path: "/prestospill" 23 | ``` 24 | ## Spill Volumes 25 | 26 | In the case of memory intensive operations, Presto allows offloading intermediate operation results to disk. The goal of this mechanism is to enable execution of queries that require amounts of memory exceeding per query or per node limits. 27 | 28 | You can add additional volumes to enable disk spilling. EmptyDir and Hostpath are two good candidates for disk spilling. -------------------------------------------------------------------------------- /docs/autoscaling.md: -------------------------------------------------------------------------------- 1 | # Autoscaling 2 | 3 | Kubernetes' [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) (HPA) automatically scales the number of pods in a replication controller, deployment, replica set or stateful set based on observed CPU utilization. 4 | 5 | Presto Operator uses HPA to scale up and down Presto workers in the presto cluster based on the CPU utilization. 6 | 7 | With autoscaling, initially the `spec.worker.count` number of workers are created. Based on `spec.worker.autoscaling.targetCPUUtilizationPercentage`, the workers are scaled up and down. The minium number of workers to which it scales down is `spec.worker.autoscaling.minReplicas`. The maximum number of workers to which it scales up is `spec.worker.autoscaling.maxReplicas`. 8 | 9 | A sample snippet of the Presto YAML. 10 | ```bash 11 | apiVersion: prestodb.io/v1alpha1 12 | kind: Presto 13 | metadata: 14 | name: mycluster 15 | spec: 16 | worker: 17 | memoryLimit: "1Gi" 18 | count: 1 19 | autoscaling: 20 | enabled: false 21 | minReplicas: 2 22 | maxReplicas: 3 23 | targetCPUUtilizationPercentage: 20 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/catalog.md: -------------------------------------------------------------------------------- 1 | # Catalogs 2 | 3 | Presto Catalogs can be added as part of YAML as key value pairs. Also, if a catalog contains credentials, the catalog can be pre-added as a secret in the Kubernetes and then the secret name can be specified in the YAML. The secrets and catalogs that are specified as key value in YAML will automatically be mounted in the catalog folder of the Presto server and workers. By default, operator adds jmx, tpch and tpcds catalogs. 4 | 5 | ## Catalogs as key value pairs 6 | 7 | Catalog contents can be specified as key value pairs in the YAML. This will be converted to a file. The name of the catalog file in the Presto cluster is the name specified in the yaml file suffixed with `.properties` 8 | 9 | ``` 10 | spec: 11 | catalogs: 12 | catalogSpec: 13 | - name: newtpch 14 | content: 15 | connector.name: tpch 16 | - name: newtpcds 17 | content: 18 | connector.name: tpcds 19 | ``` 20 | 21 | ## Catalogs as secrets 22 | 23 | Creating a secret for the catalog 24 | 25 | ```bash 26 | kubectl create secret generic tpchsecret --from-literal=mytpch='connector.name=tpch' --from-literal=myjmx='connector.name=jmx' 27 | ``` 28 | Adding the secret to the YAML file so that it gets mounted. 29 | 30 | ```bash 31 | spec: 32 | catalogs: 33 | catalogSecrets: 34 | - secretName: tpchsecret 35 | secretKey: mytpch 36 | - secretName: tpchsecret 37 | secretKey: myjmx 38 | ``` -------------------------------------------------------------------------------- /docs/caveats.md: -------------------------------------------------------------------------------- 1 | #Caveats/Future work 2 | 3 | - Currently validations for Presto Resource yaml are not done. Validating admission webhooks are something that can be added for validating presto resource YAML 4 | - Adding custom plugins to Presto is not supported/tested 5 | - Currently we do not allow the internal communication between the workers and coordinator to be encrypted. This is a conscious decision because Kubernetes network is not exposed. 6 | -------------------------------------------------------------------------------- /docs/https.md: -------------------------------------------------------------------------------- 1 | # Presto Cluster - Enabling HTTPS 2 | 3 | Currently, when HTTPS is enabled for Presto server, operator will keep the internal communication still on HTTP. This ensures that the performance of the distributed joins and other processing where data is exchanged between nodes is not impacted. And since, Presto server runs inside kubernetes environment, the HTTP ports won't be visible to an external network 4 | 5 | Presto cluster needs a server side keystore for enabling HTTPS. For accessing a HTTPS enabled Presto server, client needs to possess the certificate. For non-production environment, keytool can be used to generate the keyvalue pair. If you already have a key and certificate, the following is not needed. 6 | 7 | Generate a key and certificate for the prestoserver: 8 | ```bash 9 | # Replace PATH_FOR_KEYSTORE, PRESTO_KEYSTORE_PASSWORD, PRESTO_HOSTNAME in the command below 10 | # PRESTO_HOSTNAME is needed while verifying the key by the client. 11 | # For test environments, specify any name and then update your /etc/hosts with this name pointing to presto server 12 | # Server key would be stored in PATH_FOR_KEYSTORE as prestoserverkeystore.jks 13 | 14 | keytool -genkeypair -alias prestoserver -keyalg RSA -keystore PATH_FOR_KEYSTORE/prestoserverkeystore.jks -storepass PRESTO_KEYSTORE_PASSWORD -dname "CN=PRESTO_HOSTNAME" 15 | 16 | # use the following command to generate the keypair if you want to use IP 17 | keytool -genkeypair -alias prestoserver -ext SAN=IP:PRESTOSERVER_IP -keyalg RSA -keystore PATH_FOR_KEYSTORE/prestoserverkeystore.jks -storepass PRESTO_KEYSTORE_PASSWORD 18 | 19 | 20 | # Replace PATH_FOR_KEYSTORE, PRESTO_KEYSTORE_PASSWORD, PATH_FOR_CERTIFICATE in the command below 21 | # Client certificate would be stored in PATH_FOR_CERTIFICATE as prestoserver_clientcertificate.jks 22 | 23 | keytool -export -alias prestoserver -keystore PATH_FOR_KEYSTORE/prestoserverkeystore.jks -storepass PRESTO_KEYSTORE_PASSWORD -rfc -file PATH_FOR_CERTIFICATE/prestoserver_clientcertificate.jks 24 | ``` 25 | 26 | ## K8s Secret for HTTPS 27 | 28 | Firstly, server keystore file has to created as the K8s secret so that the Presto server can read it from there. 29 | 30 | ```bash 31 | kubectl create secret generic prestokeystore --from-file=/tmp/etc/prestoserverkeystore.jks 32 | ``` 33 | 34 | ## Presto Resource YAML - HTTPS properties 35 | 36 | Following properties now needs to be specified to create a HTTPS enabled presto server. 37 | 38 | ```bash 39 | spec.coordinator.httpsEnabled: Whether HTTPS should be enabled or not 40 | spec.coordinator.httpsKeyPairSecretName: Name of the secret of the keystore file 41 | spec.coordinator.httpsKeyPairSecretKey: Name of the file that has keystore in the secret. 42 | spec.coordinator.httpsKeyPairPassword: Password of the Keystore. This must have beeen specified while creating keystore 43 | ``` 44 | 45 | For e.g. 46 | ```bash 47 | apiVersion: prestodb.io/v1alpha1 48 | kind: Presto 49 | metadata: 50 | name: mycluster 51 | spec: 52 | coordinator: 53 | memoryLimit: "1Gi" 54 | cpuLimit: "0.5" 55 | httpsEnabled: false 56 | httpsKeyPairSecretName: "prestokeystore" 57 | httpsKeyPairSecretKey: "prestoserverkeystore.jks" 58 | httpsKeyPairPassword: "hemant" 59 | .... 60 | ``` 61 | Other HTTPS Server related properties can be specified as `spec.coordinator.additionalProps` 62 | -------------------------------------------------------------------------------- /docs/prestoresource.md: -------------------------------------------------------------------------------- 1 | # Presto Resource YAML 2 | 3 | Presto cluster can be created by specifying the cluster properties as YAML and then using that YAML to create the Presto resource. 4 | 5 | Following is an example of Presto resource YAML. 6 | 7 | ```bash 8 | apiVersion: prestodb.io/v1alpha1 9 | kind: Presto 10 | metadata: 11 | name: mycluster 12 | spec: 13 | service: 14 | type: "NodePort" 15 | port: 8100 16 | nodePort: 30002 17 | catalogs: 18 | catalogSpec: 19 | - name: newtpch 20 | content: 21 | connector.name: tpch 22 | - name: newtpcds 23 | content: 24 | connector.name: tpcds 25 | coordinator: 26 | memoryLimit: "1Gi" 27 | cpuLimit: "0.5" 28 | httpsEnabled: false 29 | httpsKeyPairSecretName: "prestokeystore" 30 | httpsKeyPairSecretKey: "prestoserverkeystore.jks" 31 | httpsKeyPairPassword: "hemant" 32 | worker: 33 | memoryLimit: "1Gi" 34 | cpuLimit: "0.5" 35 | count: 1 36 | autoscaling: 37 | enabled: false 38 | minReplicas: 2 39 | maxReplicas: 3 40 | targetCPUUtilizationPercentage: 20 41 | additionalProps: 42 | shutdown.grace-period: 10s 43 | additionalPrestoPropFiles: 44 | access-control.properties: 45 | access-control.name=read-only 46 | ``` 47 | ## Creating Presto Cluster 48 | 49 | Presto cluster can be created using this YAML. 50 | 51 | ```bash 52 | kubectl apply -f deploy/crds/prestodb.yaml 53 | ``` 54 | 55 | The main components of the YAML are service, catalogs, coordinator and worker. Coordinator and worker are used to specify the properties of coordinator and worker. All the workers in the cluster would have the same properties. 56 | -------------------------------------------------------------------------------- /docs/service.md: -------------------------------------------------------------------------------- 1 | # Services 2 | 3 | There are two services created inside Kubernetes for the communication between and with Presto processes 4 | 5 | - Headless Coordinator Discovery - This service is created so that workers can discover the coordinator using a hostname. This is internal to the operator and it cannot be configured by the user. 6 | - External service - This service is created to enable Presto Coordinator port from outside the cluster so that JDBC and REST clients can access the end point. 7 | 8 | External service can be configured to be either a ClusterIP, NodePort or LoadBalancer. By default, the external service is of type ClusterIP. 9 | 10 | External service can be configured to be of type NodePort by defining it like this. 11 | ```bash 12 | spec: 13 | service: 14 | type: "NodePort" 15 | port: 8100 16 | nodePort: 30002 17 | ``` 18 | External service can be configured to be of type LoadBalancer by defining it like this. 19 | ```bash 20 | spec: 21 | service: 22 | type: "LoadBalancer" 23 | port: 8100 24 | ``` 25 | 26 | All Kubernetes Service properties that one specifies while defining a Kubernetes service can also be defined for external service. For e.g. `externalIPs`, `loadBalancerIP`, `loadBalancerSourceRanges` 27 | 28 | -------------------------------------------------------------------------------- /docs/status.md: -------------------------------------------------------------------------------- 1 | # Managing Presto Cluster 2 | 3 | Kubernetes API and `kubectl` command can be used to list the presto servers. 4 | 5 | ```bash 6 | $ kubectl get prestos -A 7 | NAMESPACE NAME COORDINATOR CLUSTERSTATE COORDINATORCPU WORKERSCPU DESIREDWORKERS CURRENTWORKERS 8 | default mycluster 10.8.10.201:8100/30002 Ready 42% 42% 1 1 9 | 10 | ``` 11 | Kubernetes API and `kubectl` command can also be used to describe a Presto cluster. The `describe` command outputs the Status of a presto cluster along with the Spec. The status of the presto cluster looks something like this. 12 | 13 | ```bash 14 | $ kubectl describe prestos mycluster 15 | 16 | Name: mycluster 17 | Namespace: default 18 | Labels: 19 | Annotations: kubectl.kubernetes.io/last-applied-configuration: 20 | {"apiVersion":"prestodb.io/v1alpha1","kind":"Presto","metadata":{"annotations":{},"name":"mycluster","namespace":"default"},"spec":{"addit... 21 | API Version: prestodb.io/v1alpha1 22 | Kind: Presto 23 | Metadata: 24 | Creation Timestamp: 2020-06-16T10:05:32Z 25 | Generation: 1 26 | Resource Version: 62569 27 | Self Link: /apis/prestodb.io/v1alpha1/namespaces/default/prestos/mycluster 28 | UID: ea4e17e6-afb8-11ea-af85-42010a80010e 29 | Spec: 30 | Additional Presto Prop Files: 31 | access-control.properties: access-control.name=read-only 32 | Catalogs: 33 | Catalog Spec: 34 | Content: 35 | connector.name: tpch 36 | Name: newtpch 37 | Content: 38 | connector.name: tpcds 39 | Name: newtpcds 40 | Coordinator: 41 | Cpu Limit: 0.5 42 | Https Enabled: true 43 | Https Key Pair Password: hemant 44 | Https Key Pair Secret Key: prestoserverkeystore.jks 45 | Https Key Pair Secret Name: prestokeystore 46 | Memory Limit: 1Gi 47 | Service: 48 | Node Port: 30002 49 | Port: 8100 50 | Type: NodePort 51 | Worker: 52 | Additional Props: 53 | shutdown.grace-period: 10s 54 | Autoscaling: 55 | Enabled: false 56 | Max Replicas: 3 57 | Min Replicas: 2 58 | Target CPU Utilization Percentage: 20 59 | Count: 1 60 | Cpu Limit: 0.5 61 | Memory Limit: 1Gi 62 | Status: 63 | Catalog Config: catalogconfig-03f118d2 64 | Cluster State: Ready 65 | Coordinator Address: 10.8.10.201:8100/30002 66 | Coordinator CPU: 18% 67 | Coordinator Config: coordinatorconfig-03f118d2 68 | Coordinator Replicaset: coordinatorreplicaset-03f118d2 69 | Current Workers: 1 70 | Desired Workers: 1 71 | Error Reason: 72 | Headless Service: pod-discovery-03f118d2 73 | Hpa Name: 74 | Modification Time: 2020-06-16T10:08:29Z 75 | Service: external-presto-svc-03f118d2 76 | Uuid: 03f118d2-d399-4e66-8417-b5caa96988ec 77 | Worker CPU: 13% 78 | Worker Config: workerconfig-03f118d2 79 | Worker Replicaset: workerreplicaset-03f118d2zk768 80 | Events: 81 | Type Reason Age From Message 82 | ---- ------ ---- ---- ------- 83 | Normal Created 2m52s presto-controller Created Headless Service. pod-discovery-03f118d2 84 | Normal Created 2m51s presto-controller Created Service. external-presto-svc-03f118d2 85 | Normal Created 2m50s presto-controller Created Coordinator Config. coordinatorconfig-03f118d2 86 | Normal Created 2m49s presto-controller Created Worker Config. workerconfig-03f118d2 87 | Normal Created 2m48s presto-controller Created Catalog Config. catalogconfig-03f118d2 88 | Normal Created 2m48s presto-controller Created Coordinator Replicaset. coordinatorreplicaset-03f118d2 89 | Normal Created 2m47s presto-controller Created Worker Replicaset. workerreplicaset-03f118d2zk768 90 | ``` 91 | 92 | Kubernetes API and `kubectl` command can be used to delete a Presto cluster 93 | 94 | ```bash 95 | $ kubectl delete prestos mycluster 96 | ``` 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prestodb/presto-kubernetes-operator 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/go-logr/logr v0.1.0 7 | github.com/go-openapi/spec v0.19.2 8 | github.com/google/uuid v1.1.1 9 | github.com/sirupsen/logrus v1.4.2 10 | k8s.io/api v0.0.0-20190918155943-95b840bb6a1f 11 | k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 12 | k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 13 | k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf 14 | k8s.io/metrics v0.0.0-20190819143841-305e1cef1ab1 15 | sigs.k8s.io/controller-runtime v0.4.0 16 | ) 17 | -------------------------------------------------------------------------------- /nohup.out: -------------------------------------------------------------------------------- 1 | OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release. 2 | Already running 3 | -------------------------------------------------------------------------------- /pkg/apis/addtoscheme_prestodb_v1alpha1.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 5 | ) 6 | 7 | func init() { 8 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 9 | AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/apis/apis.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | ) 6 | 7 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 8 | var AddToSchemes runtime.SchemeBuilder 9 | 10 | // AddToScheme adds all Resources to the Scheme 11 | func AddToScheme(s *runtime.Scheme) error { 12 | return AddToSchemes.AddToScheme(s) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/group.go: -------------------------------------------------------------------------------- 1 | // Package prestodb contains Presto API versions. 2 | // 3 | // This file ensures Go source parsers acknowledge the prestodb package 4 | // and any child packages. It can be removed if any other Go source files are 5 | // added to this package. 6 | package prestodb 7 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Package v1alpha1 contains API Schema definitions for the prestodb v1alpha1 API group 2 | // +k8s:deepcopy-gen=package,register 3 | // +groupName=prestodb.io 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/presto_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 9 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 10 | 11 | // +k8s:openapi-gen=true 12 | type CoordinatorSpec struct { 13 | // +kubebuilder:validation:Required 14 | MemoryLimit string `json:"memoryLimit"` 15 | // +kubebuilder:validation:Optional 16 | AdditionalJVMConfig string `json:"additionalJVMConfig,omitempty"` 17 | // +kubebuilder:validation:Optional 18 | AdditionalProps map[string]string `json:"additionalProps,omitempty"` 19 | // +kubebuilder:validation:Required 20 | CpuLimit string `json:"cpuLimit"` 21 | // +kubebuilder:validation:Optional 22 | CpuRequest string `json:"cpuRequest,omitempty"` 23 | // +kubebuilder:validation:Optional 24 | HttpsEnabled bool `json:"httpsEnabled,omitempty"` 25 | // +kubebuilder:validation:Optional 26 | HttpsKeyPairSecretName string `json:"httpsKeyPairSecretName,omitempty"` 27 | // +kubebuilder:validation:Optional 28 | HttpsKeyPairSecretKey string `json:"httpsKeyPairSecretKey,omitempty"` 29 | // +kubebuilder:validation:Optional 30 | HttpsKeyPairPassword string `json:"httpsKeyPairPassword,omitempty"` 31 | } 32 | 33 | // +k8s:openapi-gen=true 34 | type WorkerSpec struct { 35 | // +kubebuilder:validation:Required 36 | MemoryLimit string `json:"memoryLimit"` 37 | // +kubebuilder:validation:Optional 38 | AdditionalJVMConfig string `json:"additionalJVMConfig,omitempty"` 39 | // +kubebuilder:validation:Optional 40 | AdditionalProps map[string]string `json:"additionalProps,omitempty"` 41 | // +kubebuilder:validation:Required 42 | CpuLimit string `json:"cpuLimit"` 43 | // +kubebuilder:validation:Optional 44 | CpuRequest string `json:"cpuRequest,omitempty"` 45 | // Optional duration in seconds the pod needs to terminate gracefully. 46 | // Value must be non-negative integer. The value zero indicates delete immediately. 47 | // If this value is nil, the default grace period will be used instead. 48 | // The grace period is the duration in seconds after the processes running in the pod are sent 49 | // a termination signal and the time when the processes are forcibly halted with a kill signal. 50 | // Set this value longer than the expected cleanup time for your process. 51 | // Defaults to 7200 seconds. 52 | // +optional 53 | // +kubebuilder:validation:Optional 54 | TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` 55 | 56 | // +kubebuilder:validation:Maximum=10000 57 | // +kubebuilder:validation:Minimum=1 58 | // +kubebuilder:validation:Required 59 | Count *int32 `json:"count"` 60 | 61 | // +kubebuilder:validation:Optional 62 | Autoscaling AutoscalingSpec `json:"autoscaling,omitempty"` 63 | } 64 | 65 | // +k8s:openapi-gen=true 66 | type AutoscalingSpec struct { 67 | // +kubebuilder:validation:Optional 68 | Enabled *bool `json:"enabled,omitempty"` 69 | // +kubebuilder:validation:Maximum=10000 70 | // +kubebuilder:validation:Minimum=1 71 | // +kubebuilder:validation:Optional 72 | MinReplicas *int32 `json:"minReplicas,omitempty"` 73 | 74 | // +kubebuilder:validation:Maximum=10000 75 | // +kubebuilder:validation:Minimum=1 76 | // +kubebuilder:validation:Optional 77 | MaxReplicas *int32 `json:"maxReplicas,omitempty"` 78 | 79 | // +kubebuilder:validation:Maximum=100 80 | // +kubebuilder:validation:Minimum=1 81 | // +kubebuilder:validation:Optional 82 | TargetCPUUtilizationPercentage *int32 `json:"targetCPUUtilizationPercentage,omitempty"` 83 | } 84 | 85 | // +k8s:openapi-gen=true 86 | type CatalogSpec struct { 87 | // +kubebuilder:validation:Required 88 | Name string `json:"name"` 89 | // +kubebuilder:validation:Required 90 | Content map[string]string `json:"content"` 91 | } 92 | 93 | // +k8s:openapi-gen=true 94 | // one has to create catalogs as secret using the following command in order to use this 95 | // kubectl create secret generic secretName --from-literal=secretKey1='connector.name=tpch' --from-literal=secretKey2='connector.name=tpcds' 96 | type CatalogSecret struct { 97 | // +kubebuilder:validation:Required 98 | SecretName string `json:"secretName,omitempty"` 99 | // +kubebuilder:validation:Required 100 | SecretKey string `json:"secretKey,omitempty"` 101 | } 102 | 103 | // +k8s:openapi-gen=true 104 | type CatalogList struct { 105 | // Secret names in the same namespace 106 | // +kubebuilder:validation:Optional 107 | CatalogSecrets []CatalogSecret `json:"catalogSecrets,omitempty"` 108 | // +kubebuilder:validation:Optional 109 | CatalogSpec []CatalogSpec `json:"catalogSpec,omitempty"` 110 | } 111 | 112 | // +k8s:openapi-gen=true 113 | // ServiceSpec describes the attributes that a user creates on a service. 114 | // Following is a copy of v1.ServiceSpec except that Ports is an optional field and 115 | // Selectors field is removed. 116 | type ServiceSpec struct { 117 | // +kubebuilder:validation:Optional 118 | NodePort *int32 `json:"nodePort,omitempty"` 119 | // +kubebuilder:validation:Optional 120 | Port *int32 `json:"port,omitempty"` 121 | 122 | // clusterIP is the IP address of the service and is usually assigned 123 | // randomly by the master. If an address is specified manually and is not in 124 | // use by others, it will be allocated to the service; otherwise, creation 125 | // of the service will fail. This field can not be changed through updates. 126 | // Valid values are "None", empty string (""), or a valid IP address. "None" 127 | // can be specified for headless services when proxying is not required. 128 | // Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if 129 | // type is ExternalName. 130 | // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies 131 | // +kubebuilder:validation:Optional 132 | ClusterIP string `json:"clusterIP,omitempty" protobuf:"bytes,3,opt,name=clusterIP"` 133 | 134 | // type determines how the Service is exposed. Defaults to ClusterIP. Valid 135 | // options are ExternalName, ClusterIP, NodePort, and LoadBalancer. 136 | // "ExternalName" maps to the specified externalName. 137 | // "ClusterIP" allocates a cluster-internal IP address for load-balancing to 138 | // endpoints. Endpoints are determined by the selector or if that is not 139 | // specified, by manual construction of an Endpoints object. If clusterIP is 140 | // "None", no virtual IP is allocated and the endpoints are published as a 141 | // set of endpoints rather than a stable IP. 142 | // "NodePort" builds on ClusterIP and allocates a port on every node which 143 | // routes to the clusterIP. 144 | // "LoadBalancer" builds on NodePort and creates an 145 | // external load-balancer (if supported in the current cloud) which routes 146 | // to the clusterIP. 147 | // More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types 148 | // +kubebuilder:validation:Optional 149 | Type v1.ServiceType `json:"type,omitempty" protobuf:"bytes,4,opt,name=type,casttype=ServiceType"` 150 | 151 | // externalIPs is a list of IP addresses for which nodes in the cluster 152 | // will also accept traffic for this service. These IPs are not managed by 153 | // Kubernetes. The user is responsible for ensuring that traffic arrives 154 | // at a node with this IP. A common example is external load-balancers 155 | // that are not part of the Kubernetes system. 156 | // +kubebuilder:validation:Optional 157 | ExternalIPs []string `json:"externalIPs,omitempty" protobuf:"bytes,5,rep,name=externalIPs"` 158 | 159 | // Supports "ClientIP" and "None". Used to maintain session affinity. 160 | // Enable client IP based session affinity. 161 | // Must be ClientIP or None. 162 | // Defaults to None. 163 | // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies 164 | // +kubebuilder:validation:Optional 165 | SessionAffinity v1.ServiceAffinity `json:"sessionAffinity,omitempty" protobuf:"bytes,7,opt,name=sessionAffinity,casttype=ServiceAffinity"` 166 | 167 | // Only applies to Service Type: LoadBalancer 168 | // LoadBalancer will get created with the IP specified in this field. 169 | // This feature depends on whether the underlying cloud-provider supports specifying 170 | // the loadBalancerIP when a load balancer is created. 171 | // This field will be ignored if the cloud-provider does not support the feature. 172 | // +kubebuilder:validation:Optional 173 | LoadBalancerIP string `json:"loadBalancerIP,omitempty" protobuf:"bytes,8,opt,name=loadBalancerIP"` 174 | 175 | // If specified and supported by the platform, this will restrict traffic through the cloud-provider 176 | // load-balancer will be restricted to the specified client IPs. This field will be ignored if the 177 | // cloud-provider does not support the feature." 178 | // More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/ 179 | // +kubebuilder:validation:Optional 180 | LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty" protobuf:"bytes,9,opt,name=loadBalancerSourceRanges"` 181 | 182 | // externalName is the external reference that kubedns or equivalent will 183 | // return as a CNAME record for this service. No proxying will be involved. 184 | // Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) 185 | // and requires Type to be ExternalName. 186 | // +kubebuilder:validation:Optional 187 | ExternalName string `json:"externalName,omitempty" protobuf:"bytes,10,opt,name=externalName"` 188 | 189 | // externalTrafficPolicy denotes if this Service desires to route external 190 | // traffic to node-local or cluster-wide endpoints. "Local" preserves the 191 | // client source IP and avoids a second hop for LoadBalancer and Nodeport 192 | // type services, but risks potentially imbalanced traffic spreading. 193 | // "Cluster" obscures the client source IP and may cause a second hop to 194 | // another node, but should have good overall load-spreading. 195 | // +kubebuilder:validation:Optional 196 | ExternalTrafficPolicy v1.ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty" protobuf:"bytes,11,opt,name=externalTrafficPolicy"` 197 | 198 | // healthCheckNodePort specifies the healthcheck nodePort for the service. 199 | // If not specified, HealthCheckNodePort is created by the service api 200 | // backend with the allocated nodePort. Will use user-specified nodePort value 201 | // if specified by the client. Only effects when Type is set to LoadBalancer 202 | // and ExternalTrafficPolicy is set to Local. 203 | // +kubebuilder:validation:Optional 204 | HealthCheckNodePort int32 `json:"healthCheckNodePort,omitempty" protobuf:"bytes,12,opt,name=healthCheckNodePort"` 205 | 206 | // publishNotReadyAddresses, when set to true, indicates that DNS implementations 207 | // must publish the notReadyAddresses of subsets for the Endpoints associated with 208 | // the Service. The default value is false. 209 | // The primary use case for setting this field is to use a StatefulSet's Headless Service 210 | // to propagate SRV records for its Pods without respect to their readiness for purpose 211 | // of peer discovery. 212 | // +kubebuilder:validation:Optional 213 | PublishNotReadyAddresses bool `json:"publishNotReadyAddresses,omitempty" protobuf:"varint,13,opt,name=publishNotReadyAddresses"` 214 | 215 | // sessionAffinityConfig contains the configurations of session affinity. 216 | // +kubebuilder:validation:Optional 217 | SessionAffinityConfig *v1.SessionAffinityConfig `json:"sessionAffinityConfig,omitempty" protobuf:"bytes,14,opt,name=sessionAffinityConfig"` 218 | 219 | // +kubebuilder:validation:Optional 220 | IPFamily *v1.IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"` 221 | } 222 | 223 | // +k8s:openapi-gen=true 224 | type HMSSpec struct { 225 | } 226 | 227 | // +k8s:openapi-gen=true 228 | type ImageSpec struct { 229 | // +kubebuilder:validation:Required 230 | Name string `json:"name"` 231 | // +kubebuilder:validation:Optional 232 | PrestoPath string `json:"prestoPath"` 233 | } 234 | 235 | // PrestoSpec defines the desired state of Presto 236 | // +k8s:openapi-gen=true 237 | type PrestoSpec struct { 238 | // INSERT ADDITIONAL SPEC FIELDS - desired state of 239 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 240 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 241 | // +kubebuilder:validation:Required 242 | Coordinator CoordinatorSpec `json:"coordinator"` 243 | // +kubebuilder:validation:Required 244 | Worker WorkerSpec `json:"worker"` 245 | // +kubebuilder:validation:Optional 246 | Catalogs CatalogList `json:"catalogs,omitempty"` 247 | // +kubebuilder:validation:Optional 248 | Service ServiceSpec `json:"service,omitempty"` 249 | // +kubebuilder:validation:Optional 250 | InternalHiveMetaStore HMSSpec `json:"internalHiveMetaStore",omitempty` 251 | // +kubebuilder:validation:Optional 252 | ImageDetails ImageSpec `json:"imageDetails",omitempty` 253 | //additionalPrestoPropFiles: 254 | // access-control.properties: | 255 | // access-control.name=read-only 256 | // event-listener.properties: | 257 | // event-listener.name=event-logger 258 | // jdbc.url=jdbc:postgresql://example.com:5432/eventlog 259 | // jdbc.user=myuser 260 | // jdbc.password=mypassword 261 | // +kubebuilder:validation:Optional 262 | AdditionalPrestoPropFiles map[string]string `json:"additionalPrestoPropFiles",omitempty` 263 | Volumes []PrestoVolumeSpec `json:"volumes,omitempty"` 264 | } 265 | 266 | type PrestoVolumeSpec struct { 267 | Name string `json:"name"` 268 | 269 | v1.VolumeSource `json:",inline"` 270 | 271 | // Mounted read-only if true, read-write otherwise (false or unspecified). 272 | // Defaults to false. 273 | // +kubebuilder:validation:Optional 274 | ReadOnly bool `json:"readOnly,omitempty"` 275 | // Path within the container at which the volume should be mounted. Must 276 | // not contain ':'. 277 | MountPath string `json:"mountPath"` 278 | // Path within the volume from which the container's volume should be mounted. 279 | // Defaults to "" (volume's root). 280 | // +kubebuilder:validation:Optional 281 | SubPath string `json:"subPath,omitempty"` 282 | // mountPropagation determines how mounts are propagated from the host 283 | // to container and the other way around. 284 | // When not set, MountPropagationNone is used. 285 | // This field is beta in 1.10. 286 | // +kubebuilder:validation:Optional 287 | MountPropagation *v1.MountPropagationMode `json:"mountPropagation,omitempty"` 288 | // Expanded path within the volume from which the container's volume should be mounted. 289 | // Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. 290 | // Defaults to "" (volume's root). 291 | // SubPathExpr and SubPath are mutually exclusive. 292 | // This field is beta in 1.15. 293 | // +kubebuilder:validation:Optional 294 | SubPathExpr string `json:"subPathExpr,omitempty"` 295 | } 296 | 297 | // PrestoStatus defines the observed state of Presto 298 | // +k8s:openapi-gen=true 299 | type PrestoStatus struct { 300 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 301 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 302 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 303 | Uuid string `json:"uuid"` 304 | // +kubebuilder:validation:Optional 305 | DesiredWorkers int32 `json:"desiredWorkers"` 306 | // +kubebuilder:validation:Optional 307 | CurrentWorkers int32 `json:"currentWorkers"` 308 | // +kubebuilder:validation:Optional 309 | HeadlessService string `json:"headlessService"` 310 | // +kubebuilder:validation:Optional 311 | Service string `json:"service"` 312 | // +kubebuilder:validation:Optional 313 | CoordinatorAddress string `json:"coordinatorAddress"` 314 | // +kubebuilder:validation:Optional 315 | CatalogConfig string `json:"catalogConfig"` 316 | // +kubebuilder:validation:Optional 317 | CoordinatorConfig string `json:"coordinatorConfig"` 318 | // +kubebuilder:validation:Optional 319 | WorkerConfig string `json:"workerConfig"` 320 | // +kubebuilder:validation:Optional 321 | WorkerReplicaset string `json:"workerReplicaset"` 322 | // +kubebuilder:validation:Optional 323 | CoordinatorReplicaset string `json:"coordinatorReplicaset"` 324 | // +kubebuilder:validation:Optional 325 | HpaName string `json:"hpaName"` 326 | // +kubebuilder:validation:Optional 327 | ClusterState ClusterState `json:"clusterState"` 328 | // +kubebuilder:validation:Optional 329 | ErrorReason string `json:"errorReason"` 330 | // +kubebuilder:validation:Optional 331 | ModificationTime metav1.Time `json:"modificationTime,omitempty"` 332 | // +kubebuilder:validation:Optional 333 | CoordinatorCPU string `json:"coordinatorCPU,omitempty"` 334 | // +kubebuilder:validation:Optional 335 | WorkerCPU string `json:"workerCPU,omitempty"` 336 | } 337 | 338 | // +k8s:openapi-gen=true 339 | type ClusterState string 340 | 341 | const ( 342 | ClusterFailedState ClusterState = "Failed" 343 | ClusterReadyState ClusterState = "Ready" 344 | ClusterPending ClusterState = "Pending" 345 | ClusterUnknown ClusterState = "Unknown" 346 | ) 347 | 348 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 349 | 350 | // Presto is the Schema for the prestos API 351 | // +k8s:openapi-gen=true 352 | // +kubebuilder:subresource:status 353 | // +kubebuilder:resource:path=prestos,scope=Namespaced 354 | // +kubebuilder:printcolumn:name="Coordinator",type="string",JSONPath=`.status.coordinatorAddress` 355 | // +kubebuilder:printcolumn:name="ClusterState",type="string",JSONPath=`.status.clusterState` 356 | // +kubebuilder:printcolumn:name="CoordinatorCPU",type="string",JSONPath=`.status.coordinatorCPU` 357 | // +kubebuilder:printcolumn:name="WorkersCPU",type="string",JSONPath=`.status.workerCPU` 358 | // +kubebuilder:printcolumn:name="DesiredWorkers",type="string",JSONPath=`.status.desiredWorkers` 359 | // +kubebuilder:printcolumn:name="CurrentWorkers",type="string",JSONPath=`.status.currentWorkers` 360 | type Presto struct { 361 | metav1.TypeMeta `json:",inline"` 362 | metav1.ObjectMeta `json:"metadata,omitempty"` 363 | 364 | Spec PrestoSpec `json:"spec,omitempty"` 365 | Status PrestoStatus `json:"status,omitempty"` 366 | } 367 | 368 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 369 | 370 | // PrestoList contains a list of Presto 371 | type PrestoList struct { 372 | metav1.TypeMeta `json:",inline"` 373 | metav1.ListMeta `json:"metadata,omitempty"` 374 | Items []Presto `json:"items"` 375 | } 376 | 377 | func init() { 378 | SchemeBuilder.Register(&Presto{}, &PrestoList{}) 379 | } 380 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/presto_webhook.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | apierrors "k8s.io/apimachinery/pkg/api/errors" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/apimachinery/pkg/util/validation/field" 8 | ctrl "sigs.k8s.io/controller-runtime" 9 | logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 10 | "sigs.k8s.io/controller-runtime/pkg/webhook" 11 | ) 12 | 13 | var log = logf.Log.WithName("webhooklog") 14 | 15 | 16 | // +kubebuilder:webhook:verbs=create;update,path=/validate-prestodb-v1alpha1-presto,mutating=false,failurePolicy=fail,groups=falarica.io,resources=prestos,versions=v1alpha1,name=validatorpresto.falarica.io 17 | 18 | func (r *Presto) SetupWebhookWithManager(mgr ctrl.Manager) error { 19 | return ctrl.NewWebhookManagedBy(mgr). 20 | For(r). 21 | Complete() 22 | } 23 | var _ webhook.Validator = &Presto{} 24 | 25 | func (r *Presto) ValidateCreate() error { 26 | log.Info("validate create", "name", r.Name) 27 | 28 | return nil 29 | } 30 | 31 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 32 | func (r *Presto) ValidateUpdate(old runtime.Object) error { 33 | log.Info("validate update", "name", r.Name) 34 | 35 | return r.validatePrestoUpdate(old) 36 | } 37 | 38 | func (r *Presto) validatePrestoUpdate(old runtime.Object) error { 39 | errs := r.validatePrestoSpec(old.(*Presto)) 40 | if len(errs) == 0 { 41 | return nil 42 | } 43 | return apierrors.NewInvalid( 44 | schema.GroupKind{Group: "prestodb.io", Kind: "Presto"}, 45 | r.Name, errs) 46 | } 47 | 48 | func (r *Presto) validatePrestoSpec(old *Presto) field.ErrorList { 49 | // The field helpers from the kubernetes API machinery help us return nicely 50 | // structured validation errors. 51 | var allErrs field.ErrorList 52 | if (old.Spec.Coordinator.CpuRequest != r.Spec.Coordinator.CpuRequest) { 53 | err := &field.Error{"FieldImmutable", "Field is Immutable", 54 | "Spec.Coordinator.CpuRequest", "Field Spec.Coordinator.CpuRequest is Immutable"} 55 | allErrs = append(allErrs, err) 56 | } 57 | return allErrs 58 | } 59 | 60 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 61 | func (r *Presto) ValidateDelete() error { 62 | return nil 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | // NOTE: Boilerplate only. Ignore this file. 2 | 3 | // Package v1alpha1 contains API Schema definitions for the prestodb v1alpha1 API group 4 | // +k8s:deepcopy-gen=package,register 5 | // +groupName=prestodb.io 6 | package v1alpha1 7 | 8 | import ( 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | "sigs.k8s.io/controller-runtime/pkg/scheme" 11 | ) 12 | 13 | var ( 14 | // SchemeGroupVersion is group version used to register these objects 15 | SchemeGroupVersion = schema.GroupVersion{Group: "prestodb.io", Version: "v1alpha1"} 16 | 17 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 18 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 19 | ) 20 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // Code generated by operator-sdk. DO NOT EDIT. 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | v1 "k8s.io/api/core/v1" 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 13 | func (in *AutoscalingSpec) DeepCopyInto(out *AutoscalingSpec) { 14 | *out = *in 15 | if in.Enabled != nil { 16 | in, out := &in.Enabled, &out.Enabled 17 | *out = new(bool) 18 | **out = **in 19 | } 20 | if in.MinReplicas != nil { 21 | in, out := &in.MinReplicas, &out.MinReplicas 22 | *out = new(int32) 23 | **out = **in 24 | } 25 | if in.MaxReplicas != nil { 26 | in, out := &in.MaxReplicas, &out.MaxReplicas 27 | *out = new(int32) 28 | **out = **in 29 | } 30 | if in.TargetCPUUtilizationPercentage != nil { 31 | in, out := &in.TargetCPUUtilizationPercentage, &out.TargetCPUUtilizationPercentage 32 | *out = new(int32) 33 | **out = **in 34 | } 35 | return 36 | } 37 | 38 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalingSpec. 39 | func (in *AutoscalingSpec) DeepCopy() *AutoscalingSpec { 40 | if in == nil { 41 | return nil 42 | } 43 | out := new(AutoscalingSpec) 44 | in.DeepCopyInto(out) 45 | return out 46 | } 47 | 48 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 49 | func (in *CatalogList) DeepCopyInto(out *CatalogList) { 50 | *out = *in 51 | if in.CatalogSecrets != nil { 52 | in, out := &in.CatalogSecrets, &out.CatalogSecrets 53 | *out = make([]CatalogSecret, len(*in)) 54 | copy(*out, *in) 55 | } 56 | if in.CatalogSpec != nil { 57 | in, out := &in.CatalogSpec, &out.CatalogSpec 58 | *out = make([]CatalogSpec, len(*in)) 59 | for i := range *in { 60 | (*in)[i].DeepCopyInto(&(*out)[i]) 61 | } 62 | } 63 | return 64 | } 65 | 66 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogList. 67 | func (in *CatalogList) DeepCopy() *CatalogList { 68 | if in == nil { 69 | return nil 70 | } 71 | out := new(CatalogList) 72 | in.DeepCopyInto(out) 73 | return out 74 | } 75 | 76 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 77 | func (in *CatalogSecret) DeepCopyInto(out *CatalogSecret) { 78 | *out = *in 79 | return 80 | } 81 | 82 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSecret. 83 | func (in *CatalogSecret) DeepCopy() *CatalogSecret { 84 | if in == nil { 85 | return nil 86 | } 87 | out := new(CatalogSecret) 88 | in.DeepCopyInto(out) 89 | return out 90 | } 91 | 92 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 93 | func (in *CatalogSpec) DeepCopyInto(out *CatalogSpec) { 94 | *out = *in 95 | if in.Content != nil { 96 | in, out := &in.Content, &out.Content 97 | *out = make(map[string]string, len(*in)) 98 | for key, val := range *in { 99 | (*out)[key] = val 100 | } 101 | } 102 | return 103 | } 104 | 105 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSpec. 106 | func (in *CatalogSpec) DeepCopy() *CatalogSpec { 107 | if in == nil { 108 | return nil 109 | } 110 | out := new(CatalogSpec) 111 | in.DeepCopyInto(out) 112 | return out 113 | } 114 | 115 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 116 | func (in *CoordinatorSpec) DeepCopyInto(out *CoordinatorSpec) { 117 | *out = *in 118 | if in.AdditionalProps != nil { 119 | in, out := &in.AdditionalProps, &out.AdditionalProps 120 | *out = make(map[string]string, len(*in)) 121 | for key, val := range *in { 122 | (*out)[key] = val 123 | } 124 | } 125 | return 126 | } 127 | 128 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoordinatorSpec. 129 | func (in *CoordinatorSpec) DeepCopy() *CoordinatorSpec { 130 | if in == nil { 131 | return nil 132 | } 133 | out := new(CoordinatorSpec) 134 | in.DeepCopyInto(out) 135 | return out 136 | } 137 | 138 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 139 | func (in *HMSSpec) DeepCopyInto(out *HMSSpec) { 140 | *out = *in 141 | return 142 | } 143 | 144 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HMSSpec. 145 | func (in *HMSSpec) DeepCopy() *HMSSpec { 146 | if in == nil { 147 | return nil 148 | } 149 | out := new(HMSSpec) 150 | in.DeepCopyInto(out) 151 | return out 152 | } 153 | 154 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 155 | func (in *ImageSpec) DeepCopyInto(out *ImageSpec) { 156 | *out = *in 157 | return 158 | } 159 | 160 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSpec. 161 | func (in *ImageSpec) DeepCopy() *ImageSpec { 162 | if in == nil { 163 | return nil 164 | } 165 | out := new(ImageSpec) 166 | in.DeepCopyInto(out) 167 | return out 168 | } 169 | 170 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 171 | func (in *Presto) DeepCopyInto(out *Presto) { 172 | *out = *in 173 | out.TypeMeta = in.TypeMeta 174 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 175 | in.Spec.DeepCopyInto(&out.Spec) 176 | in.Status.DeepCopyInto(&out.Status) 177 | return 178 | } 179 | 180 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Presto. 181 | func (in *Presto) DeepCopy() *Presto { 182 | if in == nil { 183 | return nil 184 | } 185 | out := new(Presto) 186 | in.DeepCopyInto(out) 187 | return out 188 | } 189 | 190 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 191 | func (in *Presto) DeepCopyObject() runtime.Object { 192 | if c := in.DeepCopy(); c != nil { 193 | return c 194 | } 195 | return nil 196 | } 197 | 198 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 199 | func (in *PrestoList) DeepCopyInto(out *PrestoList) { 200 | *out = *in 201 | out.TypeMeta = in.TypeMeta 202 | in.ListMeta.DeepCopyInto(&out.ListMeta) 203 | if in.Items != nil { 204 | in, out := &in.Items, &out.Items 205 | *out = make([]Presto, len(*in)) 206 | for i := range *in { 207 | (*in)[i].DeepCopyInto(&(*out)[i]) 208 | } 209 | } 210 | return 211 | } 212 | 213 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrestoList. 214 | func (in *PrestoList) DeepCopy() *PrestoList { 215 | if in == nil { 216 | return nil 217 | } 218 | out := new(PrestoList) 219 | in.DeepCopyInto(out) 220 | return out 221 | } 222 | 223 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 224 | func (in *PrestoList) DeepCopyObject() runtime.Object { 225 | if c := in.DeepCopy(); c != nil { 226 | return c 227 | } 228 | return nil 229 | } 230 | 231 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 232 | func (in *PrestoSpec) DeepCopyInto(out *PrestoSpec) { 233 | *out = *in 234 | in.Coordinator.DeepCopyInto(&out.Coordinator) 235 | in.Worker.DeepCopyInto(&out.Worker) 236 | in.Catalogs.DeepCopyInto(&out.Catalogs) 237 | in.Service.DeepCopyInto(&out.Service) 238 | out.InternalHiveMetaStore = in.InternalHiveMetaStore 239 | out.ImageDetails = in.ImageDetails 240 | if in.AdditionalPrestoPropFiles != nil { 241 | in, out := &in.AdditionalPrestoPropFiles, &out.AdditionalPrestoPropFiles 242 | *out = make(map[string]string, len(*in)) 243 | for key, val := range *in { 244 | (*out)[key] = val 245 | } 246 | } 247 | if in.Volumes != nil { 248 | in, out := &in.Volumes, &out.Volumes 249 | *out = make([]PrestoVolumeSpec, len(*in)) 250 | for i := range *in { 251 | (*in)[i].DeepCopyInto(&(*out)[i]) 252 | } 253 | } 254 | return 255 | } 256 | 257 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrestoSpec. 258 | func (in *PrestoSpec) DeepCopy() *PrestoSpec { 259 | if in == nil { 260 | return nil 261 | } 262 | out := new(PrestoSpec) 263 | in.DeepCopyInto(out) 264 | return out 265 | } 266 | 267 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 268 | func (in *PrestoStatus) DeepCopyInto(out *PrestoStatus) { 269 | *out = *in 270 | in.ModificationTime.DeepCopyInto(&out.ModificationTime) 271 | return 272 | } 273 | 274 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrestoStatus. 275 | func (in *PrestoStatus) DeepCopy() *PrestoStatus { 276 | if in == nil { 277 | return nil 278 | } 279 | out := new(PrestoStatus) 280 | in.DeepCopyInto(out) 281 | return out 282 | } 283 | 284 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 285 | func (in *PrestoVolumeSpec) DeepCopyInto(out *PrestoVolumeSpec) { 286 | *out = *in 287 | in.VolumeSource.DeepCopyInto(&out.VolumeSource) 288 | if in.MountPropagation != nil { 289 | in, out := &in.MountPropagation, &out.MountPropagation 290 | *out = new(v1.MountPropagationMode) 291 | **out = **in 292 | } 293 | return 294 | } 295 | 296 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrestoVolumeSpec. 297 | func (in *PrestoVolumeSpec) DeepCopy() *PrestoVolumeSpec { 298 | if in == nil { 299 | return nil 300 | } 301 | out := new(PrestoVolumeSpec) 302 | in.DeepCopyInto(out) 303 | return out 304 | } 305 | 306 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 307 | func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { 308 | *out = *in 309 | if in.NodePort != nil { 310 | in, out := &in.NodePort, &out.NodePort 311 | *out = new(int32) 312 | **out = **in 313 | } 314 | if in.Port != nil { 315 | in, out := &in.Port, &out.Port 316 | *out = new(int32) 317 | **out = **in 318 | } 319 | if in.ExternalIPs != nil { 320 | in, out := &in.ExternalIPs, &out.ExternalIPs 321 | *out = make([]string, len(*in)) 322 | copy(*out, *in) 323 | } 324 | if in.LoadBalancerSourceRanges != nil { 325 | in, out := &in.LoadBalancerSourceRanges, &out.LoadBalancerSourceRanges 326 | *out = make([]string, len(*in)) 327 | copy(*out, *in) 328 | } 329 | if in.SessionAffinityConfig != nil { 330 | in, out := &in.SessionAffinityConfig, &out.SessionAffinityConfig 331 | *out = new(v1.SessionAffinityConfig) 332 | (*in).DeepCopyInto(*out) 333 | } 334 | if in.IPFamily != nil { 335 | in, out := &in.IPFamily, &out.IPFamily 336 | *out = new(v1.IPFamily) 337 | **out = **in 338 | } 339 | return 340 | } 341 | 342 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. 343 | func (in *ServiceSpec) DeepCopy() *ServiceSpec { 344 | if in == nil { 345 | return nil 346 | } 347 | out := new(ServiceSpec) 348 | in.DeepCopyInto(out) 349 | return out 350 | } 351 | 352 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 353 | func (in *WorkerSpec) DeepCopyInto(out *WorkerSpec) { 354 | *out = *in 355 | if in.AdditionalProps != nil { 356 | in, out := &in.AdditionalProps, &out.AdditionalProps 357 | *out = make(map[string]string, len(*in)) 358 | for key, val := range *in { 359 | (*out)[key] = val 360 | } 361 | } 362 | if in.TerminationGracePeriodSeconds != nil { 363 | in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds 364 | *out = new(int64) 365 | **out = **in 366 | } 367 | if in.Count != nil { 368 | in, out := &in.Count, &out.Count 369 | *out = new(int32) 370 | **out = **in 371 | } 372 | in.Autoscaling.DeepCopyInto(&out.Autoscaling) 373 | return 374 | } 375 | 376 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerSpec. 377 | func (in *WorkerSpec) DeepCopy() *WorkerSpec { 378 | if in == nil { 379 | return nil 380 | } 381 | out := new(WorkerSpec) 382 | in.DeepCopyInto(out) 383 | return out 384 | } 385 | -------------------------------------------------------------------------------- /pkg/apis/prestodb/v1alpha1/zz_generated.openapi.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // This file was autogenerated by openapi-gen. Do not edit it manually! 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | spec "github.com/go-openapi/spec" 9 | common "k8s.io/kube-openapi/pkg/common" 10 | ) 11 | 12 | func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { 13 | return map[string]common.OpenAPIDefinition{ 14 | "./pkg/apis/prestodb/v1alpha1.AutoscalingSpec": schema_pkg_apis_prestodb_v1alpha1_AutoscalingSpec(ref), 15 | "./pkg/apis/prestodb/v1alpha1.CatalogList": schema_pkg_apis_prestodb_v1alpha1_CatalogList(ref), 16 | "./pkg/apis/prestodb/v1alpha1.CatalogSecret": schema_pkg_apis_prestodb_v1alpha1_CatalogSecret(ref), 17 | "./pkg/apis/prestodb/v1alpha1.CatalogSpec": schema_pkg_apis_prestodb_v1alpha1_CatalogSpec(ref), 18 | "./pkg/apis/prestodb/v1alpha1.CoordinatorSpec": schema_pkg_apis_prestodb_v1alpha1_CoordinatorSpec(ref), 19 | "./pkg/apis/prestodb/v1alpha1.HMSSpec": schema_pkg_apis_prestodb_v1alpha1_HMSSpec(ref), 20 | "./pkg/apis/prestodb/v1alpha1.ImageSpec": schema_pkg_apis_prestodb_v1alpha1_ImageSpec(ref), 21 | "./pkg/apis/prestodb/v1alpha1.Presto": schema_pkg_apis_prestodb_v1alpha1_Presto(ref), 22 | "./pkg/apis/prestodb/v1alpha1.PrestoSpec": schema_pkg_apis_prestodb_v1alpha1_PrestoSpec(ref), 23 | "./pkg/apis/prestodb/v1alpha1.PrestoStatus": schema_pkg_apis_prestodb_v1alpha1_PrestoStatus(ref), 24 | "./pkg/apis/prestodb/v1alpha1.ServiceSpec": schema_pkg_apis_prestodb_v1alpha1_ServiceSpec(ref), 25 | "./pkg/apis/prestodb/v1alpha1.WorkerSpec": schema_pkg_apis_prestodb_v1alpha1_WorkerSpec(ref), 26 | } 27 | } 28 | 29 | func schema_pkg_apis_prestodb_v1alpha1_AutoscalingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 30 | return common.OpenAPIDefinition{ 31 | Schema: spec.Schema{ 32 | SchemaProps: spec.SchemaProps{ 33 | Type: []string{"object"}, 34 | Properties: map[string]spec.Schema{ 35 | "enabled": { 36 | SchemaProps: spec.SchemaProps{ 37 | Type: []string{"boolean"}, 38 | Format: "", 39 | }, 40 | }, 41 | "minReplicas": { 42 | SchemaProps: spec.SchemaProps{ 43 | Type: []string{"integer"}, 44 | Format: "int32", 45 | }, 46 | }, 47 | "maxReplicas": { 48 | SchemaProps: spec.SchemaProps{ 49 | Type: []string{"integer"}, 50 | Format: "int32", 51 | }, 52 | }, 53 | "targetCPUUtilizationPercentage": { 54 | SchemaProps: spec.SchemaProps{ 55 | Type: []string{"integer"}, 56 | Format: "int32", 57 | }, 58 | }, 59 | }, 60 | }, 61 | }, 62 | } 63 | } 64 | 65 | func schema_pkg_apis_prestodb_v1alpha1_CatalogList(ref common.ReferenceCallback) common.OpenAPIDefinition { 66 | return common.OpenAPIDefinition{ 67 | Schema: spec.Schema{ 68 | SchemaProps: spec.SchemaProps{ 69 | Type: []string{"object"}, 70 | Properties: map[string]spec.Schema{ 71 | "catalogSecrets": { 72 | SchemaProps: spec.SchemaProps{ 73 | Description: "Secret names in the same namespace", 74 | Type: []string{"array"}, 75 | Items: &spec.SchemaOrArray{ 76 | Schema: &spec.Schema{ 77 | SchemaProps: spec.SchemaProps{ 78 | Ref: ref("./pkg/apis/prestodb/v1alpha1.CatalogSecret"), 79 | }, 80 | }, 81 | }, 82 | }, 83 | }, 84 | "catalogSpec": { 85 | SchemaProps: spec.SchemaProps{ 86 | Type: []string{"array"}, 87 | Items: &spec.SchemaOrArray{ 88 | Schema: &spec.Schema{ 89 | SchemaProps: spec.SchemaProps{ 90 | Ref: ref("./pkg/apis/prestodb/v1alpha1.CatalogSpec"), 91 | }, 92 | }, 93 | }, 94 | }, 95 | }, 96 | }, 97 | }, 98 | }, 99 | Dependencies: []string{ 100 | "./pkg/apis/prestodb/v1alpha1.CatalogSecret", "./pkg/apis/prestodb/v1alpha1.CatalogSpec"}, 101 | } 102 | } 103 | 104 | func schema_pkg_apis_prestodb_v1alpha1_CatalogSecret(ref common.ReferenceCallback) common.OpenAPIDefinition { 105 | return common.OpenAPIDefinition{ 106 | Schema: spec.Schema{ 107 | SchemaProps: spec.SchemaProps{ 108 | Description: "one has to create catalogs as secret using the following command in order to use this kubectl create secret generic secretName --from-literal=secretKey1='connector.name=tpch' --from-literal=secretKey2='connector.name=tpcds'", 109 | Type: []string{"object"}, 110 | Properties: map[string]spec.Schema{ 111 | "secretName": { 112 | SchemaProps: spec.SchemaProps{ 113 | Type: []string{"string"}, 114 | Format: "", 115 | }, 116 | }, 117 | "secretKey": { 118 | SchemaProps: spec.SchemaProps{ 119 | Type: []string{"string"}, 120 | Format: "", 121 | }, 122 | }, 123 | }, 124 | }, 125 | }, 126 | } 127 | } 128 | 129 | func schema_pkg_apis_prestodb_v1alpha1_CatalogSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 130 | return common.OpenAPIDefinition{ 131 | Schema: spec.Schema{ 132 | SchemaProps: spec.SchemaProps{ 133 | Type: []string{"object"}, 134 | Properties: map[string]spec.Schema{ 135 | "name": { 136 | SchemaProps: spec.SchemaProps{ 137 | Type: []string{"string"}, 138 | Format: "", 139 | }, 140 | }, 141 | "content": { 142 | SchemaProps: spec.SchemaProps{ 143 | Type: []string{"object"}, 144 | AdditionalProperties: &spec.SchemaOrBool{ 145 | Allows: true, 146 | Schema: &spec.Schema{ 147 | SchemaProps: spec.SchemaProps{ 148 | Type: []string{"string"}, 149 | Format: "", 150 | }, 151 | }, 152 | }, 153 | }, 154 | }, 155 | }, 156 | Required: []string{"name", "content"}, 157 | }, 158 | }, 159 | } 160 | } 161 | 162 | func schema_pkg_apis_prestodb_v1alpha1_CoordinatorSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 163 | return common.OpenAPIDefinition{ 164 | Schema: spec.Schema{ 165 | SchemaProps: spec.SchemaProps{ 166 | Type: []string{"object"}, 167 | Properties: map[string]spec.Schema{ 168 | "memoryLimit": { 169 | SchemaProps: spec.SchemaProps{ 170 | Type: []string{"string"}, 171 | Format: "", 172 | }, 173 | }, 174 | "additionalJVMConfig": { 175 | SchemaProps: spec.SchemaProps{ 176 | Type: []string{"string"}, 177 | Format: "", 178 | }, 179 | }, 180 | "additionalProps": { 181 | SchemaProps: spec.SchemaProps{ 182 | Type: []string{"object"}, 183 | AdditionalProperties: &spec.SchemaOrBool{ 184 | Allows: true, 185 | Schema: &spec.Schema{ 186 | SchemaProps: spec.SchemaProps{ 187 | Type: []string{"string"}, 188 | Format: "", 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | "cpuLimit": { 195 | SchemaProps: spec.SchemaProps{ 196 | Type: []string{"string"}, 197 | Format: "", 198 | }, 199 | }, 200 | "cpuRequest": { 201 | SchemaProps: spec.SchemaProps{ 202 | Type: []string{"string"}, 203 | Format: "", 204 | }, 205 | }, 206 | "httpsEnabled": { 207 | SchemaProps: spec.SchemaProps{ 208 | Type: []string{"boolean"}, 209 | Format: "", 210 | }, 211 | }, 212 | "httpsKeyPairSecretName": { 213 | SchemaProps: spec.SchemaProps{ 214 | Type: []string{"string"}, 215 | Format: "", 216 | }, 217 | }, 218 | "httpsKeyPairSecretKey": { 219 | SchemaProps: spec.SchemaProps{ 220 | Type: []string{"string"}, 221 | Format: "", 222 | }, 223 | }, 224 | "httpsKeyPairPassword": { 225 | SchemaProps: spec.SchemaProps{ 226 | Type: []string{"string"}, 227 | Format: "", 228 | }, 229 | }, 230 | }, 231 | Required: []string{"memoryLimit", "cpuLimit"}, 232 | }, 233 | }, 234 | } 235 | } 236 | 237 | func schema_pkg_apis_prestodb_v1alpha1_HMSSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 238 | return common.OpenAPIDefinition{ 239 | Schema: spec.Schema{ 240 | SchemaProps: spec.SchemaProps{ 241 | Type: []string{"object"}, 242 | }, 243 | }, 244 | } 245 | } 246 | 247 | func schema_pkg_apis_prestodb_v1alpha1_ImageSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 248 | return common.OpenAPIDefinition{ 249 | Schema: spec.Schema{ 250 | SchemaProps: spec.SchemaProps{ 251 | Type: []string{"object"}, 252 | Properties: map[string]spec.Schema{ 253 | "name": { 254 | SchemaProps: spec.SchemaProps{ 255 | Type: []string{"string"}, 256 | Format: "", 257 | }, 258 | }, 259 | "prestoPath": { 260 | SchemaProps: spec.SchemaProps{ 261 | Description: "\n\t+kubebuilder:validation:Optional", 262 | Type: []string{"string"}, 263 | Format: "", 264 | }, 265 | }, 266 | }, 267 | Required: []string{"name", "prestoPath"}, 268 | }, 269 | }, 270 | } 271 | } 272 | 273 | func schema_pkg_apis_prestodb_v1alpha1_Presto(ref common.ReferenceCallback) common.OpenAPIDefinition { 274 | return common.OpenAPIDefinition{ 275 | Schema: spec.Schema{ 276 | SchemaProps: spec.SchemaProps{ 277 | Description: "Presto is the Schema for the prestos API", 278 | Type: []string{"object"}, 279 | Properties: map[string]spec.Schema{ 280 | "kind": { 281 | SchemaProps: spec.SchemaProps{ 282 | Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", 283 | Type: []string{"string"}, 284 | Format: "", 285 | }, 286 | }, 287 | "apiVersion": { 288 | SchemaProps: spec.SchemaProps{ 289 | Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", 290 | Type: []string{"string"}, 291 | Format: "", 292 | }, 293 | }, 294 | "metadata": { 295 | SchemaProps: spec.SchemaProps{ 296 | Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), 297 | }, 298 | }, 299 | "spec": { 300 | SchemaProps: spec.SchemaProps{ 301 | Ref: ref("./pkg/apis/prestodb/v1alpha1.PrestoSpec"), 302 | }, 303 | }, 304 | "status": { 305 | SchemaProps: spec.SchemaProps{ 306 | Ref: ref("./pkg/apis/prestodb/v1alpha1.PrestoStatus"), 307 | }, 308 | }, 309 | }, 310 | }, 311 | }, 312 | Dependencies: []string{ 313 | "./pkg/apis/prestodb/v1alpha1.PrestoSpec", "./pkg/apis/prestodb/v1alpha1.PrestoStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, 314 | } 315 | } 316 | 317 | func schema_pkg_apis_prestodb_v1alpha1_PrestoSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 318 | return common.OpenAPIDefinition{ 319 | Schema: spec.Schema{ 320 | SchemaProps: spec.SchemaProps{ 321 | Description: "PrestoSpec defines the desired state of Presto", 322 | Type: []string{"object"}, 323 | Properties: map[string]spec.Schema{ 324 | "coordinator": { 325 | SchemaProps: spec.SchemaProps{ 326 | Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\t+kubebuilder:validation:Required", 327 | Ref: ref("./pkg/apis/prestodb/v1alpha1.CoordinatorSpec"), 328 | }, 329 | }, 330 | "worker": { 331 | SchemaProps: spec.SchemaProps{ 332 | Ref: ref("./pkg/apis/prestodb/v1alpha1.WorkerSpec"), 333 | }, 334 | }, 335 | "catalogs": { 336 | SchemaProps: spec.SchemaProps{ 337 | Ref: ref("./pkg/apis/prestodb/v1alpha1.CatalogList"), 338 | }, 339 | }, 340 | "service": { 341 | SchemaProps: spec.SchemaProps{ 342 | Ref: ref("./pkg/apis/prestodb/v1alpha1.ServiceSpec"), 343 | }, 344 | }, 345 | "internalHiveMetaStore": { 346 | SchemaProps: spec.SchemaProps{ 347 | Ref: ref("./pkg/apis/prestodb/v1alpha1.HMSSpec"), 348 | }, 349 | }, 350 | "imageDetails": { 351 | SchemaProps: spec.SchemaProps{ 352 | Ref: ref("./pkg/apis/prestodb/v1alpha1.ImageSpec"), 353 | }, 354 | }, 355 | "additionalPrestoPropFiles": { 356 | SchemaProps: spec.SchemaProps{ 357 | Description: "additionalPrestoPropFiles:\n access-control.properties: |\n access-control.name=read-only\n event-listener.properties: |\n event-listener.name=event-logger\n jdbc.url=jdbc:postgresql://example.com:5432/eventlog\n jdbc.user=myuser\n jdbc.password=mypassword", 358 | Type: []string{"object"}, 359 | AdditionalProperties: &spec.SchemaOrBool{ 360 | Allows: true, 361 | Schema: &spec.Schema{ 362 | SchemaProps: spec.SchemaProps{ 363 | Type: []string{"string"}, 364 | Format: "", 365 | }, 366 | }, 367 | }, 368 | }, 369 | }, 370 | "volumes": { 371 | SchemaProps: spec.SchemaProps{ 372 | Type: []string{"array"}, 373 | Items: &spec.SchemaOrArray{ 374 | Schema: &spec.Schema{ 375 | SchemaProps: spec.SchemaProps{ 376 | Ref: ref("./pkg/apis/prestodb/v1alpha1.PrestoVolumeSpec"), 377 | }, 378 | }, 379 | }, 380 | }, 381 | }, 382 | }, 383 | Required: []string{"coordinator", "worker", "internalHiveMetaStore", "imageDetails", "additionalPrestoPropFiles"}, 384 | }, 385 | }, 386 | Dependencies: []string{ 387 | "./pkg/apis/prestodb/v1alpha1.CatalogList", "./pkg/apis/prestodb/v1alpha1.CoordinatorSpec", "./pkg/apis/prestodb/v1alpha1.HMSSpec", "./pkg/apis/prestodb/v1alpha1.ImageSpec", "./pkg/apis/prestodb/v1alpha1.PrestoVolumeSpec", "./pkg/apis/prestodb/v1alpha1.ServiceSpec", "./pkg/apis/prestodb/v1alpha1.WorkerSpec"}, 388 | } 389 | } 390 | 391 | func schema_pkg_apis_prestodb_v1alpha1_PrestoStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { 392 | return common.OpenAPIDefinition{ 393 | Schema: spec.Schema{ 394 | SchemaProps: spec.SchemaProps{ 395 | Description: "PrestoStatus defines the observed state of Presto", 396 | Type: []string{"object"}, 397 | Properties: map[string]spec.Schema{ 398 | "uuid": { 399 | SchemaProps: spec.SchemaProps{ 400 | Description: "INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", 401 | Type: []string{"string"}, 402 | Format: "", 403 | }, 404 | }, 405 | "desiredWorkers": { 406 | SchemaProps: spec.SchemaProps{ 407 | Type: []string{"integer"}, 408 | Format: "int32", 409 | }, 410 | }, 411 | "currentWorkers": { 412 | SchemaProps: spec.SchemaProps{ 413 | Type: []string{"integer"}, 414 | Format: "int32", 415 | }, 416 | }, 417 | "headlessService": { 418 | SchemaProps: spec.SchemaProps{ 419 | Type: []string{"string"}, 420 | Format: "", 421 | }, 422 | }, 423 | "service": { 424 | SchemaProps: spec.SchemaProps{ 425 | Type: []string{"string"}, 426 | Format: "", 427 | }, 428 | }, 429 | "coordinatorAddress": { 430 | SchemaProps: spec.SchemaProps{ 431 | Type: []string{"string"}, 432 | Format: "", 433 | }, 434 | }, 435 | "catalogConfig": { 436 | SchemaProps: spec.SchemaProps{ 437 | Type: []string{"string"}, 438 | Format: "", 439 | }, 440 | }, 441 | "coordinatorConfig": { 442 | SchemaProps: spec.SchemaProps{ 443 | Type: []string{"string"}, 444 | Format: "", 445 | }, 446 | }, 447 | "workerConfig": { 448 | SchemaProps: spec.SchemaProps{ 449 | Type: []string{"string"}, 450 | Format: "", 451 | }, 452 | }, 453 | "workerReplicaset": { 454 | SchemaProps: spec.SchemaProps{ 455 | Type: []string{"string"}, 456 | Format: "", 457 | }, 458 | }, 459 | "coordinatorReplicaset": { 460 | SchemaProps: spec.SchemaProps{ 461 | Type: []string{"string"}, 462 | Format: "", 463 | }, 464 | }, 465 | "hpaName": { 466 | SchemaProps: spec.SchemaProps{ 467 | Type: []string{"string"}, 468 | Format: "", 469 | }, 470 | }, 471 | "clusterState": { 472 | SchemaProps: spec.SchemaProps{ 473 | Type: []string{"string"}, 474 | Format: "", 475 | }, 476 | }, 477 | "errorReason": { 478 | SchemaProps: spec.SchemaProps{ 479 | Type: []string{"string"}, 480 | Format: "", 481 | }, 482 | }, 483 | "modificationTime": { 484 | SchemaProps: spec.SchemaProps{ 485 | Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), 486 | }, 487 | }, 488 | "coordinatorCPU": { 489 | SchemaProps: spec.SchemaProps{ 490 | Type: []string{"string"}, 491 | Format: "", 492 | }, 493 | }, 494 | "workerCPU": { 495 | SchemaProps: spec.SchemaProps{ 496 | Type: []string{"string"}, 497 | Format: "", 498 | }, 499 | }, 500 | }, 501 | Required: []string{"uuid", "desiredWorkers", "currentWorkers", "headlessService", "service", "coordinatorAddress", "catalogConfig", "coordinatorConfig", "workerConfig", "workerReplicaset", "coordinatorReplicaset", "hpaName", "clusterState", "errorReason"}, 502 | }, 503 | }, 504 | Dependencies: []string{ 505 | "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, 506 | } 507 | } 508 | 509 | func schema_pkg_apis_prestodb_v1alpha1_ServiceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 510 | return common.OpenAPIDefinition{ 511 | Schema: spec.Schema{ 512 | SchemaProps: spec.SchemaProps{ 513 | Description: "ServiceSpec describes the attributes that a user creates on a service. Following is a copy of v1.ServiceSpec except that Ports is an optional field and Selectors field is removed.", 514 | Type: []string{"object"}, 515 | Properties: map[string]spec.Schema{ 516 | "nodePort": { 517 | SchemaProps: spec.SchemaProps{ 518 | Type: []string{"integer"}, 519 | Format: "int32", 520 | }, 521 | }, 522 | "port": { 523 | SchemaProps: spec.SchemaProps{ 524 | Type: []string{"integer"}, 525 | Format: "int32", 526 | }, 527 | }, 528 | "clusterIP": { 529 | SchemaProps: spec.SchemaProps{ 530 | Description: "clusterIP is the IP address of the service and is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. This field can not be changed through updates. Valid values are \"None\", empty string (\"\"), or a valid IP address. \"None\" can be specified for headless services when proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", 531 | Type: []string{"string"}, 532 | Format: "", 533 | }, 534 | }, 535 | "type": { 536 | SchemaProps: spec.SchemaProps{ 537 | Description: "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types", 538 | Type: []string{"string"}, 539 | Format: "", 540 | }, 541 | }, 542 | "externalIPs": { 543 | SchemaProps: spec.SchemaProps{ 544 | Description: "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.", 545 | Type: []string{"array"}, 546 | Items: &spec.SchemaOrArray{ 547 | Schema: &spec.Schema{ 548 | SchemaProps: spec.SchemaProps{ 549 | Type: []string{"string"}, 550 | Format: "", 551 | }, 552 | }, 553 | }, 554 | }, 555 | }, 556 | "sessionAffinity": { 557 | SchemaProps: spec.SchemaProps{ 558 | Description: "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", 559 | Type: []string{"string"}, 560 | Format: "", 561 | }, 562 | }, 563 | "loadBalancerIP": { 564 | SchemaProps: spec.SchemaProps{ 565 | Description: "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.", 566 | Type: []string{"string"}, 567 | Format: "", 568 | }, 569 | }, 570 | "loadBalancerSourceRanges": { 571 | SchemaProps: spec.SchemaProps{ 572 | Description: "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/", 573 | Type: []string{"array"}, 574 | Items: &spec.SchemaOrArray{ 575 | Schema: &spec.Schema{ 576 | SchemaProps: spec.SchemaProps{ 577 | Type: []string{"string"}, 578 | Format: "", 579 | }, 580 | }, 581 | }, 582 | }, 583 | }, 584 | "externalName": { 585 | SchemaProps: spec.SchemaProps{ 586 | Description: "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName.", 587 | Type: []string{"string"}, 588 | Format: "", 589 | }, 590 | }, 591 | "externalTrafficPolicy": { 592 | SchemaProps: spec.SchemaProps{ 593 | Description: "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.", 594 | Type: []string{"string"}, 595 | Format: "", 596 | }, 597 | }, 598 | "healthCheckNodePort": { 599 | SchemaProps: spec.SchemaProps{ 600 | Description: "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.", 601 | Type: []string{"integer"}, 602 | Format: "int32", 603 | }, 604 | }, 605 | "publishNotReadyAddresses": { 606 | SchemaProps: spec.SchemaProps{ 607 | Description: "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery.", 608 | Type: []string{"boolean"}, 609 | Format: "", 610 | }, 611 | }, 612 | "sessionAffinityConfig": { 613 | SchemaProps: spec.SchemaProps{ 614 | Description: "sessionAffinityConfig contains the configurations of session affinity.", 615 | Ref: ref("k8s.io/api/core/v1.SessionAffinityConfig"), 616 | }, 617 | }, 618 | "ipFamily": { 619 | SchemaProps: spec.SchemaProps{ 620 | Type: []string{"string"}, 621 | Format: "", 622 | }, 623 | }, 624 | }, 625 | }, 626 | }, 627 | Dependencies: []string{ 628 | "k8s.io/api/core/v1.SessionAffinityConfig"}, 629 | } 630 | } 631 | 632 | func schema_pkg_apis_prestodb_v1alpha1_WorkerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 633 | return common.OpenAPIDefinition{ 634 | Schema: spec.Schema{ 635 | SchemaProps: spec.SchemaProps{ 636 | Type: []string{"object"}, 637 | Properties: map[string]spec.Schema{ 638 | "memoryLimit": { 639 | SchemaProps: spec.SchemaProps{ 640 | Type: []string{"string"}, 641 | Format: "", 642 | }, 643 | }, 644 | "additionalJVMConfig": { 645 | SchemaProps: spec.SchemaProps{ 646 | Type: []string{"string"}, 647 | Format: "", 648 | }, 649 | }, 650 | "additionalProps": { 651 | SchemaProps: spec.SchemaProps{ 652 | Type: []string{"object"}, 653 | AdditionalProperties: &spec.SchemaOrBool{ 654 | Allows: true, 655 | Schema: &spec.Schema{ 656 | SchemaProps: spec.SchemaProps{ 657 | Type: []string{"string"}, 658 | Format: "", 659 | }, 660 | }, 661 | }, 662 | }, 663 | }, 664 | "cpuLimit": { 665 | SchemaProps: spec.SchemaProps{ 666 | Type: []string{"string"}, 667 | Format: "", 668 | }, 669 | }, 670 | "cpuRequest": { 671 | SchemaProps: spec.SchemaProps{ 672 | Type: []string{"string"}, 673 | Format: "", 674 | }, 675 | }, 676 | "terminationGracePeriodSeconds": { 677 | SchemaProps: spec.SchemaProps{ 678 | Description: "Optional duration in seconds the pod needs to terminate gracefully. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 7200 seconds.", 679 | Type: []string{"integer"}, 680 | Format: "int64", 681 | }, 682 | }, 683 | "count": { 684 | SchemaProps: spec.SchemaProps{ 685 | Type: []string{"integer"}, 686 | Format: "int32", 687 | }, 688 | }, 689 | "autoscaling": { 690 | SchemaProps: spec.SchemaProps{ 691 | Ref: ref("./pkg/apis/prestodb/v1alpha1.AutoscalingSpec"), 692 | }, 693 | }, 694 | }, 695 | Required: []string{"memoryLimit", "cpuLimit", "count"}, 696 | }, 697 | }, 698 | Dependencies: []string{ 699 | "./pkg/apis/prestodb/v1alpha1.AutoscalingSpec"}, 700 | } 701 | } 702 | -------------------------------------------------------------------------------- /pkg/controller/add_presto.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/prestodb/presto-kubernetes-operator/pkg/controller/presto" 5 | ) 6 | 7 | func init() { 8 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 9 | AddToManagerFuncs = append(AddToManagerFuncs, presto.Add) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/prestodb/presto-kubernetes-operator/pkg/controller/presto" 5 | "github.com/go-logr/logr" 6 | metrics "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 7 | "sigs.k8s.io/controller-runtime/pkg/manager" 8 | ) 9 | 10 | // AddToManagerFuncs is a list of functions to add all Controllers to the Manager 11 | var AddToManagerFuncs []func(manager.Manager, *metrics.MetricsV1beta1Client, 12 | presto.CommandLineParams, logr.Logger) error 13 | 14 | // AddToManager adds all Controllers to the Manager 15 | func AddToManager(m manager.Manager, metricsClient *metrics.MetricsV1beta1Client, 16 | cmdLineParams presto.CommandLineParams, log logr.Logger) error { 17 | for _, f := range AddToManagerFuncs { 18 | if err := f(m, metricsClient, cmdLineParams, log); err != nil { 19 | return err 20 | } 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/controller/presto/catalog.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "fmt" 5 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "strings" 9 | ) 10 | 11 | func createCatalogConfig(presto *v1alpha1.Presto, r *ReconcilePresto, 12 | lbls map[string]string) (bool, error) { 13 | catalogConfigName := getCatalogConfigMapName(presto.Status.Uuid) 14 | configMap, err := buildCatalogConfigMap(presto, catalogConfigName, lbls) 15 | if err != nil { 16 | return false, err 17 | } 18 | return createConfigMap(catalogConfigName, presto, r.client, configMap, lbls) 19 | } 20 | 21 | func getCatalogVolumeMount(presto *v1alpha1.Presto, podSpec *corev1.PodSpec) *corev1.VolumeMount { 22 | numOfVolProjections := len(presto.Spec.Catalogs.CatalogSecrets) + 1 23 | volumeProjectionsCatalogs := make([]corev1.VolumeProjection, numOfVolProjections) 24 | volumeProjectionsCatalogs[0] = corev1.VolumeProjection{ 25 | ConfigMap: &corev1.ConfigMapProjection{ 26 | LocalObjectReference: corev1.LocalObjectReference{ 27 | Name: getCatalogConfigMapName(presto.Status.Uuid), 28 | }, 29 | }, 30 | } 31 | for i ,catalogSecret := range presto.Spec.Catalogs.CatalogSecrets { 32 | catalogFileNames := make([]corev1.KeyToPath, 1) 33 | catalogFileNames[0] = corev1.KeyToPath{ 34 | Key: catalogSecret.SecretKey, 35 | Path: catalogSecret.SecretKey + catalogFileSuffix, 36 | } 37 | volumeProjectionsCatalogs[i + 1] = corev1.VolumeProjection{ 38 | Secret: &corev1.SecretProjection{ 39 | LocalObjectReference: corev1.LocalObjectReference{Name: catalogSecret.SecretName}, 40 | Items: catalogFileNames, 41 | }, 42 | } 43 | } 44 | 45 | catalogVolume := corev1.Volume{ 46 | Name: getCatalogVolName(presto.Status.Uuid), 47 | VolumeSource: corev1.VolumeSource{ 48 | Projected: &corev1.ProjectedVolumeSource{ 49 | Sources: volumeProjectionsCatalogs, 50 | }, 51 | }, 52 | } 53 | 54 | podSpec.Volumes = append(podSpec.Volumes, catalogVolume) 55 | return &corev1.VolumeMount{ 56 | Name: getCatalogVolName(presto.Status.Uuid), 57 | ReadOnly: true, 58 | //TODO: this depends on the docker file. Needs to be seen if it remains same 59 | MountPath: getPrestoPath(presto) + catalogMountPath, 60 | } 61 | } 62 | 63 | func getDefaultCatalogs(presto *v1alpha1.Presto) map[string]string{ 64 | var defaultCatalogs = map[string]string { 65 | "jmx" : "connector.name=jmx\n", 66 | "tpch" : "connector.name=tpch\n", 67 | "tpcds" : "connector.name=tpcds\n", 68 | } 69 | // add default catalog if catalogs with same name are are not already added 70 | prunedDefaultCatalog := make(map[string]string) 71 | for catalogName, content := range defaultCatalogs { 72 | catalogPresentInSpec := false 73 | for _, specCatalog := range presto.Spec.Catalogs.CatalogSpec { 74 | if specCatalog.Name == catalogName { 75 | catalogPresentInSpec = true 76 | } 77 | } 78 | for _, specCatalogSecret := range presto.Spec.Catalogs.CatalogSecrets { 79 | if specCatalogSecret.SecretKey == catalogName { 80 | catalogPresentInSpec = true 81 | } 82 | } 83 | if !catalogPresentInSpec { 84 | prunedDefaultCatalog[catalogName] = content 85 | } 86 | } 87 | return prunedDefaultCatalog 88 | } 89 | 90 | func buildCatalogConfigMap(presto *v1alpha1.Presto, 91 | catalogConfigName string, labels map[string]string) (*corev1.ConfigMap, error) { 92 | catalogData := make(map[string]string) 93 | for _, catalog := range presto.Spec.Catalogs.CatalogSpec { 94 | var sb strings.Builder 95 | for key, value := range catalog.Content { 96 | sb.WriteString(fmt.Sprintf("%s=%s\n", key, value)) 97 | } 98 | catalogData[catalog.Name + catalogFileSuffix] = sb.String() 99 | } 100 | 101 | defaultCatalogs := getDefaultCatalogs(presto) 102 | for k, v := range defaultCatalogs { 103 | // add .properties to the catalog name. as we are not asking that as part of catalog name 104 | catalogData[k + catalogFileSuffix] = v 105 | } 106 | 107 | return &corev1.ConfigMap{ 108 | ObjectMeta: metav1.ObjectMeta{ 109 | Name: catalogConfigName, 110 | Namespace: presto.Namespace, 111 | Labels: labels, 112 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 113 | }, 114 | Data: catalogData, 115 | }, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/controller/presto/common_util.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/labels" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | func getPodDiscoveryServiceName(clusterUUID string) string { 14 | return "pod-discovery-" + clusterUUID[:8] 15 | } 16 | 17 | func getPodDiscoveryServiceLabel(clusterUUID string) (string, string) { 18 | return "pod-discovery", clusterUUID 19 | } 20 | 21 | func getCoordinatorInternalName(clusterUUID string) string { 22 | return fmt.Sprintf("%s.%s", 23 | getCoordinatorContainerName(clusterUUID), 24 | getPodDiscoveryServiceName(clusterUUID)) 25 | } 26 | 27 | func getExternalServiceName(clusterUUID string) string { 28 | return "external-presto-svc-" + clusterUUID[:8] 29 | } 30 | 31 | func getExternalServiceLabel(clusterUUID string) (string, string) { 32 | return "external-presto-svc", clusterUUID 33 | } 34 | 35 | func getCoordinatorPodName(clusterUUID string) string { 36 | return "coordinatorpod-" + clusterUUID[:8] 37 | } 38 | func getCoordinatorReplicaset(clusterUUID string) string { 39 | return "coordinatorreplicaset-" + clusterUUID[:8] 40 | } 41 | 42 | func getWorkerReplicaSet(clusterUUID string) string { 43 | return "workerreplicaset-" + clusterUUID[:8] 44 | } 45 | 46 | func getCatalogConfigMapName(clusterUUID string) string { 47 | return "catalogconfig-" + clusterUUID[:8] 48 | } 49 | 50 | func getCatalogVolName(clusterUUID string) string { 51 | return "catalogvol-" + clusterUUID[:8] 52 | } 53 | 54 | func getHTTPSSecretVolName(clusterUUID string) string { 55 | return "httpssecret-" + clusterUUID[:8] 56 | } 57 | 58 | func getHPAName(clusterUUID string) string { 59 | return "hpa-" + clusterUUID[:8] 60 | } 61 | 62 | func getCoordinatorContainerName(clusterUUID string) string { 63 | return "coordinatorcontainer-" + clusterUUID[:8] 64 | } 65 | 66 | func getCoordinatorConfigMapName(clusterUUID string) string { 67 | return "coordinatorconfig-" + clusterUUID[:8] 68 | } 69 | 70 | func getCoordinatorConfigVolumeName(clusterUUID string) string { 71 | return "coordinatorconfvol-" + clusterUUID[:8] 72 | } 73 | func getWorkerContainerPrefix(clusterUUID string) string { 74 | return fmt.Sprintf("workerrcontainer-%s", clusterUUID[:8]) 75 | } 76 | 77 | func getCoordinatorPodLabel(clusterUUID string) (string, string) { 78 | return "coordinator", clusterUUID 79 | } 80 | 81 | func getWorkerPodLabel(clusterUUID string) (string, string) { 82 | return "worker", clusterUUID 83 | } 84 | 85 | func getCoordinatorPodLabels(baseLabels map[string]string, clusterUUID string) map[string]string { 86 | lbls := make(map[string]string) 87 | for key, value := range baseLabels { 88 | lbls[key] = value 89 | } 90 | k,v := getCoordinatorPodLabel(clusterUUID) 91 | lbls[k] = v 92 | return lbls; 93 | } 94 | 95 | func getWorkerPodLabels(baseLabels map[string]string, clusterUUID string) map[string]string { 96 | lbls := make(map[string]string) 97 | for key, value := range baseLabels { 98 | lbls[key] = value 99 | } 100 | k,v := getWorkerPodLabel(clusterUUID) 101 | lbls[k] = v 102 | return lbls; 103 | } 104 | 105 | func getWorkerConfigMapName(clusterUUID string) string { 106 | return "workerconfig-" + clusterUUID[:8] 107 | } 108 | func getWorkerConfigVolumeName(clusterUUID string) string { 109 | return "workerconfvol-" + clusterUUID[:8] 110 | } 111 | 112 | func getOwnerReference(app *v1alpha1.Presto) *metav1.OwnerReference { 113 | controller := true 114 | return &metav1.OwnerReference{ 115 | APIVersion: app.APIVersion, 116 | Kind: app.Kind, 117 | Name: app.Name, 118 | UID: app.UID, 119 | Controller: &controller, 120 | } 121 | } 122 | 123 | func createConfigMap(configMapName string, presto *v1alpha1.Presto, c client.Client, 124 | configMap *corev1.ConfigMap, lbls map[string]string) (bool, error) { 125 | oldConfigMaps := &corev1.ConfigMapList{} 126 | err := c.List(context.TODO(), 127 | oldConfigMaps, 128 | &client.ListOptions{ 129 | Namespace: presto.Namespace, 130 | LabelSelector: labels.SelectorFromSet(lbls), 131 | }) 132 | if err != nil { 133 | return false, err 134 | } 135 | for _, cm := range oldConfigMaps.Items { 136 | if cm.Name == configMapName { 137 | return false, nil 138 | } 139 | } 140 | createErr := c.Create(context.Background(), configMap) 141 | if createErr != nil { 142 | return false, createErr 143 | } else { 144 | return true, createErr 145 | } 146 | } 147 | 148 | func getPrestoPath(presto *v1alpha1.Presto) string { 149 | prestoPath := presto.Spec.ImageDetails.PrestoPath 150 | if len(presto.Spec.ImageDetails.PrestoPath) == 0 { 151 | prestoPath = mountPath 152 | } 153 | return prestoPath 154 | } 155 | -------------------------------------------------------------------------------- /pkg/controller/presto/constants.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | const ( 4 | nodePropertiesKey = "node.properties" 5 | configPropertiesKey = "config.properties" 6 | jvmConfigKey = "jvm.config" 7 | prestoPort = 8080 8 | mountPath = "/etc/presto" 9 | httpsVolPath = "/etc/httpssecret" 10 | prestoShutdownScript = "presto_shutdown.sh" 11 | DefaultTerminationGracePeriodSeconds = 7200 12 | catalogFileSuffix = ".properties" 13 | catalogMountPath = "/catalog/" 14 | // script called during shutdown. Picked from OneOneStar repo https://gist.github.com/oneonestar/ea75a608d58aa7e40cc952ad20e5a31a 15 | // Have made it a string so that a separate file is not needed at the run time. 16 | // the string has to be formatted to pass the mountpath of config.properties 17 | shutdownScriptContent = ` 18 | #!/bin/bash 19 | # This works for worker only. Coordinator doesn't support graceful shutdown. 20 | # This script will block until the server has actually shutdown. 21 | set -x 22 | http_port="$(cat {MOUNT_PATH}/config.properties | grep 'http-server.http.port' | sed 's/^.*=\(.*\)$/\1/')" 23 | https_port="$(cat {MOUNT_PATH}/config.properties | grep 'http-server.https.port' | sed 's/^.*=\(.*\)$/\1/')" 24 | 25 | if [ -n "$http_port" ] ; then 26 | res=$(curl -s -o /dev/null -w "%{http_code}" -XPUT --data '"SHUTTING_DOWN"' -H "Content-type: application/json" http://localhost:${http_port}/v1/info/state) 27 | fi 28 | 29 | if [ -z "$res" -o "$res" != "200" ] && [ -n "$https_port" ]; then 30 | res=$(curl -k -s -o /dev/null -w "%{http_code}" -XPUT --data '"SHUTTING_DOWN"' -H "Content-type: application/json" https://localhost:${https_port}/v1/info/state) 31 | fi 32 | 33 | if [ -z "$res" -o "$res" != "200" ] ; then 34 | # Failed to send the shutdown request. 35 | exit -1 36 | else 37 | # Server is shutting down. Block until the server is actually down. 38 | while curl http://localhost:${http_port}/v1/info/state; do 39 | sleep 3 40 | done 41 | fi 42 | ` 43 | ) 44 | -------------------------------------------------------------------------------- /pkg/controller/presto/errors.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | type OperatorError struct { 4 | errormsg string 5 | } 6 | 7 | func (e *OperatorError) Error() string { 8 | return e.errormsg 9 | } 10 | -------------------------------------------------------------------------------- /pkg/controller/presto/hpa_handler.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 8 | v1 "k8s.io/api/apps/v1" 9 | autoscalingv1 "k8s.io/api/autoscaling/v1" 10 | "k8s.io/apimachinery/pkg/api/errors" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/labels" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | ) 15 | 16 | /* 17 | There are certain global properties which we might want to change. for e.g 18 | --horizontal-pod-autoscaler-downscale-stabilization duration Default: 5m0s 19 | The period for which autoscaler will look backwards and not scale down below 20 | any recommendation it made during that period. 21 | --horizontal-pod-autoscaler-sync-period duration Default: 15s 22 | The period for syncing the number of pods in horizontal pod autoscaler. 23 | However, these cannot be changed for managed K8s services like GKE and EKS 24 | https://stackoverflow.com/questions/55815094/how-to-change-horizontal-pod-autoscaler-sync-period-field-in-kube-controller-m 25 | https://stackoverflow.com/questions/46317275/change-the-horizontal-pod-autoscaler-sync-period-with-gke 26 | */ 27 | func handleReplicaSet( 28 | r *ReconcilePresto, 29 | presto *v1alpha1.Presto, 30 | workerReplicaSet *v1.ReplicaSet, 31 | lbls map[string]string, 32 | ctx context.Context) (bool, bool, bool, error) { 33 | 34 | autoScalingEnabled := checkAutoscalingEnabled(presto) 35 | created := false 36 | updated := false 37 | deleted := false 38 | 39 | var hpa *autoscalingv1.HorizontalPodAutoscaler 40 | exists := true 41 | hpa, err := getPrestoHPA(r, presto, lbls) 42 | if errors.IsNotFound(err) { 43 | exists = false 44 | } else if err != nil { 45 | return created, updated, deleted, err 46 | } 47 | if exists { 48 | if autoScalingEnabled { 49 | if autoscaleSpecChanged(hpa, presto) { 50 | r.log.Info(fmt.Sprintf("HPA spec will be updated")) 51 | hpa, err := createHPASpec(presto, workerReplicaSet, 52 | lbls, hpa.ObjectMeta.ResourceVersion) 53 | if err != nil { 54 | return created, updated, deleted, err 55 | } 56 | err = r.client.Update(ctx, hpa) 57 | if err != nil && !errors.IsAlreadyExists(err) { 58 | r.log.Error(err, "Failed to update HPA spec") 59 | return created, updated, deleted, err 60 | } 61 | updated = true 62 | } 63 | } else { 64 | r.log.Info(fmt.Sprintf("Since autoscaling is disabled, deleting HPA spec")) 65 | 66 | err := r.client.Delete(ctx, hpa) 67 | if err != nil { 68 | r.log.Error(err, "Failed to delete HPA spec") 69 | return created, updated, deleted, err 70 | } 71 | deleted = true 72 | } 73 | } else if autoScalingEnabled { 74 | r.log.Info(fmt.Sprintf("Creating HPA Spec")) 75 | hpa, err := createHPASpec(presto, workerReplicaSet, lbls, "") 76 | if err != nil { 77 | return created, updated, deleted, err 78 | } 79 | err = r.client.Create(ctx, hpa) 80 | if err != nil && !errors.IsAlreadyExists(err) { 81 | r.log.Error(err, "Failed to create HPA Spec") 82 | return created, updated, deleted, err 83 | } 84 | created = true 85 | } 86 | return created, updated, deleted, nil 87 | } 88 | 89 | func autoscaleSpecChanged(hpa *autoscalingv1.HorizontalPodAutoscaler, 90 | presto *v1alpha1.Presto) bool { 91 | return hpa.Spec.MaxReplicas != *presto.Spec.Worker.Autoscaling.MaxReplicas || 92 | *hpa.Spec.MinReplicas != *presto.Spec.Worker.Autoscaling.MinReplicas || 93 | *hpa.Spec.TargetCPUUtilizationPercentage != *presto.Spec.Worker.Autoscaling.TargetCPUUtilizationPercentage 94 | } 95 | 96 | func getPrestoHPA(r *ReconcilePresto,presto *v1alpha1.Presto, 97 | lbls map[string]string) (*autoscalingv1.HorizontalPodAutoscaler, error) { 98 | prestoHPA := &autoscalingv1.HorizontalPodAutoscalerList{} 99 | err := r.client.List(context.TODO(), 100 | prestoHPA, 101 | &client.ListOptions{ 102 | Namespace: presto.Namespace, 103 | LabelSelector: labels.SelectorFromSet(lbls), 104 | }) 105 | if errors.IsNotFound(err) { 106 | return nil, errors.NewNotFound(v1.Resource("HPA"), "") 107 | } 108 | if err != nil { 109 | r.log.Error(err, "failed to list existing Presto HPA") 110 | return nil, err 111 | } 112 | if len(prestoHPA.Items) == 0 { 113 | return nil, errors.NewNotFound(v1.Resource("HPA"), "") 114 | } 115 | return &prestoHPA.Items[0], nil 116 | } 117 | 118 | func checkAutoscalingEnabled(presto *v1alpha1.Presto) bool { 119 | if presto.Spec.Worker.Autoscaling.Enabled == nil { 120 | return false 121 | } else { 122 | return *presto.Spec.Worker.Autoscaling.Enabled 123 | } 124 | } 125 | 126 | func isCreatedByHpaController(hpa *autoscalingv1.HorizontalPodAutoscaler, presto *v1alpha1.Presto) bool { 127 | for _, ref := range hpa.OwnerReferences { 128 | if ref.Name == presto.Name && ref.Kind == presto.Kind { 129 | return true 130 | } 131 | } 132 | return false 133 | } 134 | 135 | func createHPASpec(presto *v1alpha1.Presto, 136 | workerReplicaSet *v1.ReplicaSet, 137 | lbls map[string]string, resourceVersion string) (*autoscalingv1.HorizontalPodAutoscaler, error) { 138 | 139 | if presto.Spec.Worker.Autoscaling.MinReplicas == nil { 140 | return nil, &OperatorError{errormsg: "MinReplicas cannot be null"} 141 | } 142 | minReplicas := *presto.Spec.Worker.Autoscaling.MinReplicas 143 | 144 | if presto.Spec.Worker.Autoscaling.MaxReplicas == nil { 145 | return nil, &OperatorError{errormsg: "MaxReplicas cannot be null"} 146 | } 147 | maxReplicas := *presto.Spec.Worker.Autoscaling.MaxReplicas 148 | 149 | if presto.Spec.Worker.Autoscaling.TargetCPUUtilizationPercentage == nil { 150 | return nil, &OperatorError{errormsg: "TargetCPUUtilizationPercentage cannot be null"} 151 | } 152 | targetCPUUtilization := *presto.Spec.Worker.Autoscaling.TargetCPUUtilizationPercentage 153 | 154 | hpa := &autoscalingv1.HorizontalPodAutoscaler{ 155 | ObjectMeta: metav1.ObjectMeta{ 156 | Name: getHPAName(presto.Status.Uuid), 157 | Namespace: presto.Namespace, 158 | Labels: lbls, 159 | // resource version for optimistic concurrency control 160 | ResourceVersion: resourceVersion, 161 | OwnerReferences: []metav1.OwnerReference{ 162 | *getOwnerReference(presto), 163 | }, 164 | }, 165 | Spec: autoscalingv1.HorizontalPodAutoscalerSpec{ 166 | ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{ 167 | APIVersion: workerReplicaSet.APIVersion, 168 | Kind: workerReplicaSet.Kind, 169 | Name: workerReplicaSet.Name, 170 | }, 171 | MinReplicas: &minReplicas, 172 | MaxReplicas: maxReplicas, 173 | TargetCPUUtilizationPercentage: &targetCPUUtilization, 174 | }, 175 | } 176 | 177 | return hpa, nil 178 | } 179 | -------------------------------------------------------------------------------- /pkg/controller/presto/presto_controller.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | prestodbv1alpha1 "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 7 | "github.com/go-logr/logr" 8 | "github.com/google/uuid" 9 | v1 "k8s.io/api/apps/v1" 10 | corev1 "k8s.io/api/core/v1" 11 | "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/apimachinery/pkg/api/resource" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/labels" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | "k8s.io/client-go/tools/record" 17 | metrics "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 18 | ctrl "sigs.k8s.io/controller-runtime" 19 | "sigs.k8s.io/controller-runtime/pkg/client" 20 | "sigs.k8s.io/controller-runtime/pkg/controller" 21 | "sigs.k8s.io/controller-runtime/pkg/event" 22 | "sigs.k8s.io/controller-runtime/pkg/handler" 23 | "sigs.k8s.io/controller-runtime/pkg/manager" 24 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 25 | "sigs.k8s.io/controller-runtime/pkg/source" 26 | "sync" 27 | "time" 28 | ) 29 | 30 | const ( 31 | ControllerName string = "presto-controller" 32 | ) 33 | type CommandLineParams struct { 34 | StatusUpdateInterval int 35 | } 36 | 37 | 38 | /** 39 | * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller 40 | * business logic. Delete these comments after modifying this file.* 41 | */ 42 | 43 | // Add creates a new Presto Controller and adds it to the Manager. The Manager will set fields on the Controller 44 | // and Start it when the Manager is Started. 45 | func Add(mgr manager.Manager, metricsClient *metrics.MetricsV1beta1Client, 46 | cmdLineParams CommandLineParams, log logr.Logger) error { 47 | return add(mgr, newReconciler(mgr, metricsClient, cmdLineParams, log)) 48 | } 49 | 50 | // newReconciler returns a new reconcile.Reconciler 51 | func newReconciler(mgr manager.Manager, metricsClient *metrics.MetricsV1beta1Client, 52 | cmdLineParams CommandLineParams, log logr.Logger) ReconcilePresto { 53 | periodicPrestoEventChannel := make(chan event.GenericEvent) 54 | 55 | r := ReconcilePresto{ 56 | client: mgr.GetClient(), 57 | log: log, 58 | scheme: mgr.GetScheme(), 59 | metricsClient: metricsClient, 60 | eventRecorder: mgr.GetEventRecorderFor(ControllerName), 61 | periodicPrestoEvents: periodicPrestoEventChannel, 62 | registeredPrestos: new(sync.Map), 63 | } 64 | // a goroutine that sends periodic events for the registered prestos to a channel 65 | // that channel is being watched by the controlller. 66 | go func(r *ReconcilePresto) { 67 | ticker := time.NewTicker(time.Duration(cmdLineParams.StatusUpdateInterval) * time.Second) 68 | r.log.Info("Starting periodic event generation for presto clusters ") 69 | for { 70 | select { 71 | case <-ticker.C: 72 | r.registeredPrestos.Range(func(key, value interface{}) bool { 73 | v := value.(event.GenericEvent) 74 | periodicPrestoEventChannel <- v 75 | return true 76 | }) 77 | } 78 | } 79 | }(&r) 80 | 81 | return r 82 | } 83 | 84 | // add adds a new Controller to mgr with r as the reconcile.Reconciler 85 | func add(mgr manager.Manager, r ReconcilePresto) error { 86 | // Create a new controller. Set MaxConcurrentReconciles as 1. So there is only one thread 87 | c, err := controller.New(ControllerName, mgr, controller.Options{MaxConcurrentReconciles: 1, Reconciler: &r}) 88 | if err != nil { 89 | return err 90 | } 91 | // watch the channel where periodic events are published for registered presto clusters 92 | err = c.Watch(&source.Channel{Source: r.periodicPrestoEvents}, &handler.EnqueueRequestForObject{}) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | // Watch for changes to primary resource Presto 98 | err = c.Watch(&source.Kind{Type: &prestodbv1alpha1.Presto{}}, &handler.EnqueueRequestForObject{}, GenerationChangedPredicate{}) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{ 104 | IsController: true, 105 | OwnerType: &prestodbv1alpha1.Presto{}, 106 | }) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForOwner{ 112 | IsController: true, 113 | OwnerType: &prestodbv1alpha1.Presto{}, 114 | }) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | err = c.Watch(&source.Kind{Type: &v1.ReplicaSet{}}, &handler.EnqueueRequestForOwner{ 120 | IsController: true, 121 | OwnerType: &prestodbv1alpha1.Presto{}, 122 | }) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // blank assignment to verify that ReconcilePresto implements reconcile.Reconciler 131 | var _ reconcile.Reconciler = &ReconcilePresto{} 132 | 133 | // ReconcilePresto reconciles a Presto object 134 | type ReconcilePresto struct { 135 | // This client, initialized using mgr.Client() above, is a split client 136 | // that reads objects from the cache and writes to the apiserver 137 | client client.Client 138 | log logr.Logger 139 | scheme *runtime.Scheme 140 | metricsClient *metrics.MetricsV1beta1Client 141 | eventRecorder record.EventRecorder 142 | periodicPrestoEvents chan event.GenericEvent 143 | registeredPrestos *sync.Map 144 | } 145 | 146 | // Reconcile reads that state of the cluster for a Presto object and makes changes based on the state read 147 | // and what is in the Presto.Spec 148 | func (r *ReconcilePresto) Reconcile(request reconcile.Request) (reconcile.Result, error) { 149 | ctx := context.Background() 150 | _ = r.log.WithValues("deployment", request.NamespacedName) 151 | presto := &prestodbv1alpha1.Presto{} 152 | err := r.client.Get(ctx, request.NamespacedName, presto) 153 | if err != nil { 154 | if errors.IsNotFound(err) { 155 | r.log.Info("Un-Registering for periodic events " + request.NamespacedName.String()) 156 | r.registeredPrestos.Delete(request.NamespacedName) 157 | // Object not found, return. Created objects are automatically garbage collected. 158 | // For additional cleanup logic use finalizers. 159 | return reconcile.Result{}, nil 160 | } 161 | // Error reading the object - requeue the request. 162 | return reconcile.Result{}, err 163 | } 164 | 165 | _, ok := r.registeredPrestos.Load(request.NamespacedName) 166 | if !ok { 167 | r.log.Info("Registering for periodic events " + request.NamespacedName.String()) 168 | r.registeredPrestos.Store(request.NamespacedName, event.GenericEvent{ 169 | Meta: presto.GetObjectMeta(), 170 | Object: presto, 171 | }) 172 | } 173 | 174 | if len(presto.Status.Uuid) == 0 { 175 | clusterUUID := uuid.New().String() 176 | updated, err := r.updateStatus(presto, 177 | ctx, ClusterUpdateAction{ 178 | clusterUUID: &clusterUUID, 179 | }) 180 | if err != nil { 181 | r.log.Error(err, "failed to create and update cluster UUID") 182 | return reconcile.Result{}, err 183 | } 184 | if updated { 185 | // updated cluster ID. Requeue again so that cluster creation can begin 186 | return reconcile.Result{RequeueAfter: 0*time.Millisecond}, nil 187 | } 188 | } 189 | 190 | baseLabels := labels.Set{ 191 | "clusterUUID": presto.Status.Uuid, 192 | "clusterName": presto.Name, 193 | } 194 | 195 | err, changesMade := r.headlessServiceConfig(presto, baseLabels, ctx) 196 | if err != nil { 197 | return reconcile.Result{}, err 198 | } 199 | if changesMade { 200 | return reconcile.Result{}, nil 201 | } 202 | 203 | err, changesMade, _ = r.serviceConfig(presto, baseLabels, ctx) 204 | if err != nil { 205 | return reconcile.Result{}, err 206 | } 207 | if changesMade { 208 | return reconcile.Result{}, nil 209 | } 210 | 211 | err, changesMade = r.coordinatorConfig(presto, baseLabels, ctx) 212 | if err != nil { 213 | return reconcile.Result{}, err 214 | } 215 | if changesMade { 216 | return reconcile.Result{}, nil 217 | } 218 | 219 | err, changesMade = r.workerConfig(presto, baseLabels, ctx) 220 | if err != nil { 221 | return reconcile.Result{}, err 222 | } 223 | if changesMade { 224 | return reconcile.Result{}, nil 225 | } 226 | 227 | err, changesMade = r.catalogConfig(presto, baseLabels, ctx) 228 | if err != nil { 229 | return reconcile.Result{}, err 230 | } 231 | if changesMade { 232 | return reconcile.Result{}, nil 233 | } 234 | 235 | err, changesMade = r.coordinatorReplicaset(presto, baseLabels, ctx) 236 | if err != nil { 237 | return reconcile.Result{}, err 238 | } 239 | if changesMade { 240 | return reconcile.Result{}, nil 241 | } 242 | 243 | err, changesMade, workerReplicaSet := r.workerReplicaset(presto, baseLabels, ctx) 244 | if err != nil { 245 | return reconcile.Result{}, err 246 | } 247 | if changesMade { 248 | return reconcile.Result{}, nil 249 | } 250 | 251 | err, changesMade = r.hpaReplicaset(presto, baseLabels, ctx, workerReplicaSet) 252 | if err != nil { 253 | return reconcile.Result{}, err 254 | } 255 | if changesMade { 256 | return reconcile.Result{}, nil 257 | } 258 | 259 | // Update the state based on coordinator pod phase 260 | _, coordinatorPodPhase := r.getCoordinatorPodPhase(presto, baseLabels) 261 | if coordinatorPodPhase == corev1.PodPending { 262 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 263 | clusterState: prestodbv1alpha1.ClusterPending, 264 | workerReplicaSet: workerReplicaSet, 265 | }) 266 | } else if coordinatorPodPhase == corev1.PodFailed { 267 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 268 | clusterState: prestodbv1alpha1.ClusterFailedState, 269 | workerReplicaSet: workerReplicaSet, 270 | }) 271 | }else if coordinatorPodPhase == corev1.PodRunning { 272 | workerCPU := fmt.Sprintf("%d%%",r.getCPUUsage(presto, false)) 273 | coordinatorCPU := fmt.Sprintf("%d%%",r.getCPUUsage(presto, true)) 274 | 275 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 276 | clusterState: prestodbv1alpha1.ClusterReadyState, 277 | workerReplicaSet: workerReplicaSet, 278 | workerCPUUsage: &workerCPU, 279 | coordinatorCPUUsage: &coordinatorCPU, 280 | }) 281 | } else { 282 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 283 | clusterState: prestodbv1alpha1.ClusterUnknown, 284 | workerReplicaSet: workerReplicaSet, 285 | }) 286 | } 287 | return ctrl.Result{}, nil 288 | } 289 | 290 | func (r *ReconcilePresto) headlessServiceConfig(presto *prestodbv1alpha1.Presto, 291 | baseLabels map[string]string, 292 | ctx context.Context) (error, bool) { 293 | changesMade := false 294 | err, created := createHeadLessPodDiscoverySvc(presto, r, baseLabels) 295 | if err != nil { 296 | r.log.Error(err, "failed to create headless service for pods") 297 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 298 | "Failed to create headless service for pods %s", err.Error()) 299 | errorReason := fmt.Sprintf("Failed to create headless service for pods %s", err.Error()) 300 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 301 | errorReason: &errorReason, 302 | clusterState: prestodbv1alpha1.ClusterFailedState, 303 | }) 304 | return err, changesMade 305 | } else { 306 | if created{ 307 | r.log.Info("created headless service ") 308 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 309 | "Created Headless Service. %s", getPodDiscoveryServiceName(presto.Status.Uuid)) 310 | changesMade = true 311 | } 312 | if created { 313 | headlessSvc := getPodDiscoveryServiceName(presto.Status.Uuid) 314 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 315 | headlessService: &headlessSvc, 316 | clusterState: prestodbv1alpha1.ClusterPending, 317 | }) 318 | } 319 | } 320 | return err, changesMade 321 | } 322 | 323 | func (r *ReconcilePresto) serviceConfig(presto *prestodbv1alpha1.Presto, 324 | baseLabels map[string]string, 325 | ctx context.Context) (error, bool, *corev1.Service) { 326 | changesMade := false 327 | err, service, created := createOrGetService(presto, r, baseLabels) 328 | if err != nil { 329 | r.log.Error(err, "failed to create service for pods") 330 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 331 | "Failed to create service for pods %s", err.Error()) 332 | errorReason := fmt.Sprintf("Failed to create service for pods %s", err.Error()) 333 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 334 | errorReason: &errorReason, 335 | clusterState: prestodbv1alpha1.ClusterFailedState, 336 | }) 337 | return err, changesMade, nil 338 | } else { 339 | if created{ 340 | r.log.Info("created service ") 341 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 342 | "Created Service. %s", getExternalServiceName(presto.Status.Uuid)) 343 | changesMade = true 344 | } 345 | if created || len(presto.Status.CoordinatorAddress) == 0 { 346 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 347 | service: service, 348 | clusterState: prestodbv1alpha1.ClusterPending, 349 | }) 350 | } 351 | } 352 | return err, changesMade, service 353 | } 354 | 355 | func (r *ReconcilePresto) coordinatorConfig(presto *prestodbv1alpha1.Presto, 356 | baseLabels map[string]string, 357 | ctx context.Context) (error, bool) { 358 | created, err := createCoordinatorConfig(presto, r.client, baseLabels) 359 | if err != nil { 360 | r.log.Error(err, "failed to create coordinator config map") 361 | errorReason := fmt.Sprintf("Failed to create coordinator config map %s", err.Error()) 362 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 363 | errorReason: &errorReason, 364 | clusterState: prestodbv1alpha1.ClusterFailedState, 365 | }) 366 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 367 | "failed to create coordinator config map %s", err.Error()) 368 | return err, false 369 | } 370 | if created { 371 | cm := getCoordinatorConfigMapName(presto.Status.Uuid) 372 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 373 | coordinatorConfMap: &cm, 374 | clusterState: prestodbv1alpha1.ClusterPending, 375 | }) 376 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 377 | "Created Coordinator Config. %s", cm) 378 | r.log.Info("created coordinator config map") 379 | } 380 | return nil, created 381 | } 382 | func (r *ReconcilePresto) workerConfig(presto *prestodbv1alpha1.Presto, 383 | baseLabels map[string]string, 384 | ctx context.Context) (error, bool) { 385 | created, err := createWorkerConfig(presto, r.client, baseLabels) 386 | if err != nil { 387 | r.log.Error(err, "failed to create worker config map") 388 | errorReason := fmt.Sprintf("Failed to create worker config map %s", err.Error()) 389 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 390 | errorReason: &errorReason, 391 | clusterState: prestodbv1alpha1.ClusterFailedState, 392 | }) 393 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 394 | "Failed to create worker config map %s", err.Error()) 395 | return err, created 396 | } 397 | if created { 398 | wm := getWorkerConfigMapName(presto.Status.Uuid) 399 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 400 | workerConfMap: &wm, 401 | clusterState: prestodbv1alpha1.ClusterPending, 402 | }) 403 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 404 | "Created Worker Config. %s", wm) 405 | r.log.Info("created worker config map") 406 | } 407 | return nil, created 408 | } 409 | func (r *ReconcilePresto) catalogConfig(presto *prestodbv1alpha1.Presto, 410 | baseLabels map[string]string, 411 | ctx context.Context) (error, bool) { 412 | created, err := createCatalogConfig(presto, r, baseLabels) 413 | if err != nil { 414 | r.log.Error(err, "failed to create catalog config map") 415 | errorReason := fmt.Sprintf("Failed to create catalog config map %s", err.Error()) 416 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 417 | errorReason: &errorReason, 418 | clusterState: prestodbv1alpha1.ClusterFailedState, 419 | }) 420 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 421 | "Failed to create catalog config map %s", err.Error()) 422 | return err, created 423 | } 424 | if created { 425 | cc := getCatalogConfigMapName(presto.Status.Uuid) 426 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 427 | catalogConfMap: &cc, 428 | clusterState: prestodbv1alpha1.ClusterPending, 429 | }) 430 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 431 | "Created Catalog Config. %s", cc) 432 | r.log.Info("catalog catalog config map") 433 | created = true 434 | } 435 | return nil, created 436 | } 437 | func (r *ReconcilePresto) coordinatorReplicaset(presto *prestodbv1alpha1.Presto, 438 | baseLabels map[string]string, 439 | ctx context.Context) (error, bool) { 440 | _, created, err := createUpdateReplicaSetForCoordinator(r, presto, 441 | getCoordinatorPodLabels(baseLabels, presto.Status.Uuid)) 442 | if err != nil { 443 | r.log.Error(err, "failed to create/update coordinator replicaset ") 444 | errorReason := fmt.Sprintf("Failed to create coordinator replicaset %s", err.Error()) 445 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 446 | errorReason: &errorReason, 447 | clusterState: prestodbv1alpha1.ClusterFailedState, 448 | }) 449 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 450 | "Failed to create/update coordinator replicaset %s", err.Error()) 451 | return err, created 452 | } 453 | if created { 454 | cr := getCoordinatorReplicaset(presto.Status.Uuid) 455 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 456 | coordinatorReplicaSetName: &cr, 457 | clusterState: prestodbv1alpha1.ClusterPending, 458 | }) 459 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 460 | "Created Coordinator Replicaset. %s", cr) 461 | r.log.Info("created coordinator replicaset") 462 | } 463 | return nil, created 464 | } 465 | func (r *ReconcilePresto) workerReplicaset(presto *prestodbv1alpha1.Presto, 466 | baseLabels map[string]string, 467 | ctx context.Context) (error, bool, *v1.ReplicaSet) { 468 | changesMade := false 469 | workerReplicaSet, created, updated, err := createUpdateReplicaSetForWorker(r, presto, 470 | getWorkerPodLabels(baseLabels, presto.Status.Uuid)) 471 | if err != nil { 472 | errorReason := fmt.Sprintf("Failed to create worker replicaset %s", err.Error()) 473 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 474 | errorReason: &errorReason, 475 | clusterState: prestodbv1alpha1.ClusterFailedState, 476 | }) 477 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 478 | "Failed to create worker replicaset %s", err.Error()) 479 | return err, changesMade, workerReplicaSet 480 | } 481 | if updated { 482 | r.log.Info("updated worker replicaset") 483 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Updated", 484 | "Updated Worker Replicaset. %s ", workerReplicaSet.Name) 485 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 486 | workerReplicaSet: workerReplicaSet, 487 | clusterState: prestodbv1alpha1.ClusterPending, 488 | }) 489 | changesMade = true 490 | } 491 | if created { 492 | r.log.Info("created worker replicaset") 493 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 494 | workerReplicaSet: workerReplicaSet, 495 | clusterState: prestodbv1alpha1.ClusterPending, 496 | }) 497 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 498 | "Created Worker Replicaset. %s", workerReplicaSet.Name) 499 | changesMade = true 500 | } 501 | return nil, changesMade, workerReplicaSet 502 | } 503 | 504 | func (r *ReconcilePresto) hpaReplicaset(presto *prestodbv1alpha1.Presto, 505 | baseLabels map[string]string, 506 | ctx context.Context, 507 | workerReplicaSet *v1.ReplicaSet) (error, bool) { 508 | changesMade := false 509 | created, updated, deleted, err := handleReplicaSet(r, presto, workerReplicaSet, baseLabels, ctx) 510 | if err != nil { 511 | r.log.Error(err, "failed to create/update autoscale replicaset") 512 | errorReason := fmt.Sprintf("Failed to create autoscale config %s", err.Error()) 513 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 514 | errorReason: &errorReason, 515 | clusterState: prestodbv1alpha1.ClusterFailedState, 516 | }) 517 | r.eventRecorder.Eventf(presto, corev1.EventTypeWarning, "Failed", 518 | "failed to create/update autoscale replicaset %s", err.Error()) 519 | return err, false 520 | } 521 | if created { 522 | hpaName := getHPAName(presto.Status.Uuid) 523 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 524 | hpaName: &hpaName, 525 | clusterState: prestodbv1alpha1.ClusterPending, 526 | }) 527 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Created", 528 | "Created HPA. %s", hpaName) 529 | changesMade = true 530 | } 531 | if updated { 532 | hpaName := getHPAName(presto.Status.Uuid) 533 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Updated", 534 | "Updated HPA. %s", hpaName) 535 | changesMade = true 536 | } 537 | if deleted { 538 | hpaName := getHPAName(presto.Status.Uuid) 539 | hpa := "" 540 | r.eventRecorder.Eventf(presto, corev1.EventTypeNormal, "Deleted", 541 | "Deleted HPA. %s", hpaName) 542 | r.updateStatus(presto, ctx,ClusterUpdateAction{ 543 | hpaName: &hpa, 544 | clusterState: prestodbv1alpha1.ClusterPending, 545 | }) 546 | changesMade = true 547 | } 548 | return nil, changesMade 549 | } 550 | type ClusterUpdateAction struct { 551 | clusterUUID *string 552 | service *corev1.Service 553 | headlessService *string 554 | workerConfMap *string 555 | coordinatorConfMap *string 556 | catalogConfMap *string 557 | coordinatorReplicaSetName *string 558 | hpaName *string 559 | workerReplicaSet *v1.ReplicaSet 560 | clusterState prestodbv1alpha1.ClusterState 561 | errorReason *string 562 | coordinatorCPUUsage *string 563 | workerCPUUsage *string 564 | } 565 | 566 | func (r *ReconcilePresto) updateStatus(presto *prestodbv1alpha1.Presto, 567 | ctx context.Context, updateAction ClusterUpdateAction) (bool, error) { 568 | prestoCopy, err := r.getPresto(presto) 569 | if err != nil { 570 | r.log.Error(err, "failed to update the presto status") 571 | return false, err 572 | } 573 | if prestoCopy == nil { 574 | prestoCopy = presto 575 | } 576 | update := false 577 | // update UUID 578 | if updateAction.clusterUUID != nil{ 579 | prestoCopy.Status.Uuid = *updateAction.clusterUUID 580 | update = true 581 | } 582 | if updateAction.service != nil{ 583 | prestoCopy.Status.Service = updateAction.service.Name 584 | prestoCopy.Status.CoordinatorAddress = fmt.Sprintf("%s:%d/%d", updateAction.service.Spec.ClusterIP, 585 | updateAction.service.Spec.Ports[0].Port, updateAction.service.Spec.Ports[0].NodePort) 586 | update = true 587 | } 588 | if updateAction.headlessService != nil{ 589 | prestoCopy.Status.HeadlessService = *updateAction.headlessService 590 | update = true 591 | } 592 | if updateAction.workerConfMap != nil{ 593 | prestoCopy.Status.WorkerConfig = *updateAction.workerConfMap 594 | update = true 595 | } 596 | if updateAction.coordinatorConfMap != nil{ 597 | prestoCopy.Status.CoordinatorConfig = *updateAction.coordinatorConfMap 598 | update = true 599 | } 600 | if updateAction.catalogConfMap != nil{ 601 | prestoCopy.Status.CatalogConfig = *updateAction.catalogConfMap 602 | update = true 603 | } 604 | if updateAction.coordinatorReplicaSetName != nil{ 605 | prestoCopy.Status.CoordinatorReplicaset = *updateAction.coordinatorReplicaSetName 606 | update = true 607 | } 608 | if updateAction.hpaName != nil{ 609 | prestoCopy.Status.HpaName = *updateAction.hpaName 610 | update = true 611 | } 612 | if len(updateAction.clusterState) != 0 { 613 | prestoCopy.Status.ClusterState = updateAction.clusterState 614 | update = true 615 | } 616 | if updateAction.errorReason != nil{ 617 | prestoCopy.Status.ErrorReason = *updateAction.errorReason 618 | update = true 619 | } 620 | if updateAction.workerCPUUsage != nil{ 621 | prestoCopy.Status.WorkerCPU = *updateAction.workerCPUUsage 622 | update = true 623 | } 624 | if updateAction.coordinatorCPUUsage != nil{ 625 | prestoCopy.Status.CoordinatorCPU = *updateAction.coordinatorCPUUsage 626 | update = true 627 | } 628 | // Update worker count 629 | if updateAction.workerReplicaSet != nil { 630 | prestoCopy.Status.WorkerReplicaset = updateAction.workerReplicaSet.Name 631 | prestoCopy.Status.DesiredWorkers = *updateAction.workerReplicaSet.Spec.Replicas 632 | prestoCopy.Status.CurrentWorkers = updateAction.workerReplicaSet.Status.AvailableReplicas 633 | update = true 634 | } 635 | 636 | if update { 637 | prestoCopy.Status.ModificationTime = metav1.Now() 638 | err = r.client.Status().Update(ctx, prestoCopy) 639 | if err != nil { 640 | r.log.Error(err, "failed to update the presto status") 641 | return false, err 642 | } 643 | } 644 | return update, nil 645 | } 646 | 647 | func (r *ReconcilePresto) getCPUUsage(presto *prestodbv1alpha1.Presto, 648 | isCoordinator bool) int64 { 649 | var podLabels map[string]string 650 | var containerPrefix string 651 | var cpuLimit string 652 | if isCoordinator { 653 | wk, wv := getCoordinatorPodLabel(presto.Status.Uuid) 654 | containerPrefix = getCoordinatorContainerName(presto.Status.Uuid) 655 | podLabels = labels.Set{wk: wv} 656 | cpuLimit = presto.Spec.Coordinator.CpuLimit 657 | } else { 658 | wk, wv := getWorkerPodLabel(presto.Status.Uuid) 659 | containerPrefix = getWorkerContainerPrefix(presto.Status.Uuid) 660 | podLabels = labels.Set{wk: wv} 661 | cpuLimit = presto.Spec.Worker.CpuLimit 662 | } 663 | 664 | podsMetrics, err := r.metricsClient.PodMetricses(presto.Namespace).List(metav1.ListOptions{ 665 | TypeMeta: metav1.TypeMeta{ 666 | Kind: presto.Kind, 667 | APIVersion: presto.APIVersion, 668 | }, 669 | LabelSelector: labels.SelectorFromSet(podLabels).String(), 670 | }) 671 | if err != nil { 672 | r.log.Error(err, "Failed to fetch CPU stats") 673 | return 0 674 | } 675 | // No need to check the error here as the CPULimit has already been parsed 676 | allottedQuantity, _ := resource.ParseQuantity(cpuLimit) 677 | allotted := allottedQuantity.MilliValue() 678 | var totalAllotted int64 = 0 679 | var totalUsed int64 = 0 680 | // get the cumulative CPU usage across all workers or for a coordinator 681 | for _, pm := range podsMetrics.Items { 682 | for _, cm := range pm.Containers { 683 | for k, used := range cm.Usage { 684 | if k == corev1.ResourceCPU && 685 | cm.Name == containerPrefix { 686 | totalAllotted = totalAllotted + allotted 687 | totalUsed = totalUsed + used.MilliValue() 688 | } 689 | } 690 | } 691 | } 692 | if totalAllotted == 0 { 693 | return 0 694 | } else { 695 | return (totalUsed * 100) / totalAllotted 696 | } 697 | } 698 | 699 | func (r* ReconcilePresto) getCoordinatorPodPhase(presto *prestodbv1alpha1.Presto, 700 | baseLabels map[string]string) (error, corev1.PodPhase) { 701 | coordinatorPodList := &corev1.PodList{} 702 | err := r.client.List(context.TODO(), 703 | coordinatorPodList, 704 | &client.ListOptions{ 705 | Namespace: presto.Namespace, 706 | LabelSelector: labels.SelectorFromSet(getCoordinatorPodLabels(baseLabels, presto.Status.Uuid)), 707 | }) 708 | if err != nil { 709 | r.log.Error(err, "Failed to find the state of coordinator pod") 710 | return err, corev1.PodUnknown 711 | } 712 | if len(coordinatorPodList.Items) != 0 { 713 | return nil, coordinatorPodList.Items[0].Status.Phase 714 | } 715 | return nil, corev1.PodUnknown 716 | } 717 | func (r *ReconcilePresto) getPresto(oldPrestoObj *prestodbv1alpha1.Presto) (*prestodbv1alpha1.Presto, error) { 718 | prestoCluster := &prestodbv1alpha1.PrestoList{} 719 | err := r.client.List(context.TODO(), 720 | prestoCluster, 721 | &client.ListOptions{ 722 | Namespace: oldPrestoObj.Namespace, 723 | LabelSelector: labels.SelectorFromSet(oldPrestoObj.Labels), 724 | }) 725 | if err != nil { 726 | return nil, err 727 | } 728 | for _, pc := range prestoCluster.Items { 729 | if pc.Name == oldPrestoObj.Name { 730 | return &pc, nil 731 | } 732 | } 733 | return nil, nil 734 | } 735 | -------------------------------------------------------------------------------- /pkg/controller/presto/presto_pods.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | prestodbv1alpha1 "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 7 | v1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | "k8s.io/apimachinery/pkg/api/resource" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/labels" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | ) 15 | 16 | // returns replicaSet, created, updated, error 17 | func createUpdateReplicaSetForWorker(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 18 | lbls map[string]string) (*v1.ReplicaSet,bool, bool, error) { 19 | updated := false 20 | created := false 21 | // Get the replicaSet with the name specified in PrestoCluster.spec 22 | replicaSet, err := getReplicaSet(r, presto, getWorkerPodLabel) 23 | // If the resource doesn't exist, we'll create it 24 | if errors.IsNotFound(err) { 25 | replicaSet, err = createReplicaSetForWorker(r, presto, lbls, *presto.Spec.Worker.Count) 26 | if err != nil { 27 | r.log.Error(err,"Failed to create replicaSet object") 28 | return nil, created, updated, err 29 | } 30 | err = r.client.Create(context.Background(), replicaSet) 31 | if err != nil { 32 | r.log.Error(err,"Failed to create replicaSet") 33 | return nil, created, updated, err 34 | } 35 | created = true 36 | } else { 37 | if err != nil { 38 | r.log.Error(err, "Failed to get replicaSet") 39 | return nil, created, updated, err 40 | } 41 | // worker count shall be updated only if autoscaling is not enabled 42 | if presto.Spec.Worker.Autoscaling.Enabled == nil || 43 | !*presto.Spec.Worker.Autoscaling.Enabled { 44 | // If the number of the replicas is not equal the current replicas on the ReplicaSet, update it 45 | if presto.Spec.Worker.Count != nil && *presto.Spec.Worker.Count != *replicaSet.Spec.Replicas { 46 | r.log.Info(fmt.Sprintf("PrestoCluster %s workerCount: %d, replicaSet replicas: %d", 47 | presto.Name, *presto.Spec.Worker.Count, *replicaSet.Spec.Replicas)) 48 | replicaSetCopy := replicaSet.DeepCopy() 49 | replicaSetCopy.Spec.Replicas = presto.Spec.Worker.Count 50 | err = r.client.Update(context.Background(), replicaSetCopy) 51 | replicaSet = replicaSetCopy 52 | if err != nil { 53 | return nil, created, updated, err 54 | } 55 | updated = true 56 | } 57 | } 58 | } 59 | return replicaSet, created, updated, nil 60 | } 61 | 62 | func createReplicaSetForWorker(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 63 | lbls map[string]string, workerCount int32) (*v1.ReplicaSet, error) { 64 | podSpec, err := getPrestoWorkerPod(r, presto) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return &v1.ReplicaSet{ 69 | ObjectMeta: metav1.ObjectMeta{ 70 | GenerateName: getWorkerReplicaSet(presto.Status.Uuid), 71 | Namespace: presto.Namespace, 72 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 73 | Labels: lbls, 74 | }, 75 | Spec: v1.ReplicaSetSpec{ 76 | Replicas: func() *int32 { i := workerCount; return &i }(), 77 | Selector: &metav1.LabelSelector{ 78 | MatchLabels: lbls, 79 | }, 80 | Template: corev1.PodTemplateSpec{ 81 | ObjectMeta: metav1.ObjectMeta{ 82 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 83 | Namespace: presto.Namespace, 84 | Labels: lbls, 85 | }, 86 | Spec: *podSpec, 87 | }, 88 | }, 89 | }, nil 90 | } 91 | 92 | // returns podCreated, podDeleted, error 93 | func getPrestoWorkerPod(r *ReconcilePresto, presto *prestodbv1alpha1.Presto) (*corev1.PodSpec, error) { 94 | limitResource := corev1.ResourceList{} 95 | requestResource := corev1.ResourceList{} 96 | var err error 97 | limitResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Worker.CpuLimit) 98 | if err != nil { 99 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.CpuLimit: " + 100 | "'%v': %v", presto.Spec.Worker.CpuLimit, err)} 101 | } 102 | limitResource[corev1.ResourceMemory], err = resource.ParseQuantity(presto.Spec.Worker.MemoryLimit) 103 | if err != nil { 104 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.MemoryLimit: " + 105 | "'%v': %v", presto.Spec.Worker.MemoryLimit, err)} 106 | } 107 | if len(presto.Spec.Worker.CpuRequest) == 0 { 108 | // set CPURequest same as limit if not specified 109 | requestResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Worker.CpuLimit) 110 | if err != nil { 111 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.CpuLimit: " + 112 | "'%v': %v", presto.Spec.Worker.CpuLimit, err)} 113 | } 114 | } else { 115 | requestResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Worker.CpuRequest) 116 | if err != nil { 117 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.CpuRequest: " + 118 | "'%v': %v", presto.Spec.Worker.CpuRequest, err)} 119 | } 120 | } 121 | return createPrestoPodSpec(r, presto, false, 122 | limitResource, requestResource), nil 123 | } 124 | 125 | 126 | func getReplicaSet(r *ReconcilePresto,presto *prestodbv1alpha1.Presto, 127 | getLabel func(string)(string, string)) (*v1.ReplicaSet, error) { 128 | existingReplicaSet := &v1.ReplicaSetList{} 129 | wk, wv := getLabel(presto.Status.Uuid) 130 | err := r.client.List(context.TODO(), 131 | existingReplicaSet, 132 | &client.ListOptions{ 133 | Namespace: presto.Namespace, 134 | LabelSelector: labels.SelectorFromSet(labels.Set{wk: wv}), 135 | }) 136 | if errors.IsNotFound(err) { 137 | return nil, errors.NewNotFound(v1.Resource("replicasets"), "") 138 | } 139 | if err != nil { 140 | r.log.Error(err, "failed to list existing Presto replicaset") 141 | return nil, err 142 | } 143 | if len(existingReplicaSet.Items) == 0 { 144 | return nil, errors.NewNotFound(v1.Resource("replicasets"), "") 145 | } 146 | return &existingReplicaSet.Items[0], nil 147 | } 148 | 149 | // returns replicaSet, created, error 150 | func createUpdateReplicaSetForCoordinator(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 151 | lbls map[string]string) (*v1.ReplicaSet, bool, error) { 152 | created := false 153 | // Get the replicaSet with the name specified in PrestoCluster.spec 154 | replicaSet, err := getReplicaSet(r, presto, getCoordinatorPodLabel) 155 | // If the resource doesn't exist, we'll create it 156 | if errors.IsNotFound(err) { 157 | replicaSet, err = createReplicaSetForCoordinator(r, presto, lbls) 158 | if err != nil { 159 | return nil,created, err 160 | } 161 | err = r.client.Create(context.Background(), replicaSet) 162 | if err != nil { 163 | return nil, created, err 164 | } 165 | created = true 166 | } 167 | if err != nil { 168 | return nil, created, err 169 | } 170 | return replicaSet, created, nil 171 | } 172 | 173 | func createReplicaSetForCoordinator(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 174 | lbls map[string]string) (*v1.ReplicaSet, error) { 175 | podSpec, err := getPrestoCoordinatorPodSpec(r, presto, lbls) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return &v1.ReplicaSet{ 180 | ObjectMeta: metav1.ObjectMeta{ 181 | GenerateName: getCoordinatorReplicaset(presto.Status.Uuid), 182 | Namespace: presto.Namespace, 183 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 184 | Labels: lbls, 185 | }, 186 | Spec: v1.ReplicaSetSpec{ 187 | Replicas: func() *int32 { i := int32(1); return &i }(), 188 | Selector: &metav1.LabelSelector{ 189 | MatchLabels: lbls, 190 | }, 191 | Template: corev1.PodTemplateSpec{ 192 | ObjectMeta: metav1.ObjectMeta{ 193 | Namespace: presto.Namespace, 194 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 195 | Labels: lbls, 196 | }, 197 | Spec: *podSpec, 198 | }, 199 | }, 200 | }, nil 201 | } 202 | 203 | // returns whether the pod has been created or not 204 | func getPrestoCoordinatorPodSpec(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 205 | labels map[string]string) (*corev1.PodSpec, error) { 206 | limitResource := corev1.ResourceList{} 207 | requestResource := corev1.ResourceList{} 208 | var err error 209 | limitResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Coordinator.CpuLimit) 210 | if err != nil { 211 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Coordinator.CpuLimit: " + 212 | "'%v': %v", presto.Spec.Coordinator.CpuLimit, err)} 213 | } 214 | limitResource[corev1.ResourceMemory], err = resource.ParseQuantity(presto.Spec.Worker.MemoryLimit) 215 | if err != nil { 216 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.MemoryLimit: " + 217 | "'%v': %v", presto.Spec.Worker.MemoryLimit, err)} 218 | } 219 | if len(presto.Spec.Coordinator.CpuRequest) == 0 { 220 | // set CPURequest same as limit if not specified 221 | requestResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Coordinator.CpuLimit) 222 | if err != nil { 223 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Coordinator.CpuLimit: " + 224 | "'%v': %v", presto.Spec.Coordinator.CpuLimit, err)} 225 | } 226 | } else { 227 | requestResource[corev1.ResourceCPU], err = resource.ParseQuantity(presto.Spec.Coordinator.CpuRequest) 228 | if err != nil { 229 | return nil, &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Coordinator.CpuRequest: " + 230 | "'%v': %v", presto.Spec.Coordinator.CpuRequest, err)} 231 | } 232 | } 233 | return createPrestoPodSpec(r, presto, true, 234 | limitResource, requestResource), nil 235 | } 236 | 237 | // Returns podCreated, error 238 | func createPrestoPodSpec(r *ReconcilePresto, presto *prestodbv1alpha1.Presto, 239 | isCoordinator bool, limitResource corev1.ResourceList, 240 | requestResource corev1.ResourceList) *corev1.PodSpec { 241 | imageName := presto.Spec.ImageDetails.Name 242 | if len(imageName) == 0 { 243 | imageName = "prestosql/presto:333" 244 | } 245 | 246 | var containerPrefix string 247 | var lifecycle *corev1.Lifecycle 248 | var terminationGraceSeconds *int64 = nil 249 | 250 | if isCoordinator { 251 | lifecycle = nil 252 | containerPrefix = getCoordinatorContainerName(presto.Status.Uuid) 253 | } else { 254 | containerPrefix = getWorkerContainerPrefix(presto.Status.Uuid) 255 | if presto.Spec.Worker.TerminationGracePeriodSeconds == nil { 256 | defaultGraceSeconds := int64(DefaultTerminationGracePeriodSeconds) 257 | terminationGraceSeconds = &defaultGraceSeconds 258 | } else { 259 | terminationGraceSeconds = presto.Spec.Worker.TerminationGracePeriodSeconds 260 | } 261 | 262 | lifecycle = &corev1.Lifecycle{ 263 | PostStart: nil, 264 | PreStop: &corev1.Handler{ 265 | Exec: &corev1.ExecAction{ 266 | Command: []string{"/bin/sh", fmt.Sprintf("%s/%s", getPrestoPath(presto), prestoShutdownScript)}, 267 | }, 268 | }, 269 | } 270 | } 271 | 272 | podSpec := &corev1.PodSpec{ 273 | TerminationGracePeriodSeconds: terminationGraceSeconds, 274 | Containers: []corev1.Container { 275 | { 276 | Name: containerPrefix, 277 | Image: imageName, 278 | Resources: corev1.ResourceRequirements{ 279 | Limits: limitResource, 280 | Requests: requestResource, 281 | }, 282 | Lifecycle: lifecycle, 283 | }, 284 | }, 285 | } 286 | 287 | if isCoordinator { 288 | podSpec.Hostname = getCoordinatorContainerName(presto.Status.Uuid) 289 | podSpec.Subdomain = getPodDiscoveryServiceName(presto.Status.Uuid) 290 | } 291 | catalogMount := getCatalogVolumeMount(presto, podSpec) 292 | propsMount := getPropsVolumeMount(presto, podSpec, isCoordinator) 293 | appendAdditionalVolumes(presto, &podSpec.Volumes) 294 | podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, *propsMount) 295 | podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, *catalogMount) 296 | if isCoordinator && presto.Spec.Coordinator.HttpsEnabled { 297 | httpsMount := getHTTPSVolumeMount(presto, podSpec) 298 | podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, *httpsMount) 299 | } 300 | appendAdditionalVolumeMounts(presto, &podSpec.Containers[0].VolumeMounts) 301 | return podSpec 302 | } 303 | 304 | func appendAdditionalVolumes(presto *prestodbv1alpha1.Presto, 305 | vols *[]corev1.Volume) { 306 | for _, volSpec := range presto.Spec.Volumes { 307 | vol := corev1.Volume { 308 | Name: volSpec.Name, 309 | VolumeSource: volSpec.VolumeSource, 310 | } 311 | *vols = append(*vols, vol) 312 | } 313 | } 314 | 315 | func appendAdditionalVolumeMounts(presto *prestodbv1alpha1.Presto, 316 | volMounts *[]corev1.VolumeMount) { 317 | for _, volSpec := range presto.Spec.Volumes { 318 | volMount := corev1.VolumeMount{ 319 | Name: volSpec.Name, 320 | ReadOnly: volSpec.ReadOnly, 321 | MountPath: volSpec.MountPath, 322 | SubPath: volSpec.SubPath, 323 | MountPropagation: volSpec.MountPropagation, 324 | SubPathExpr: volSpec.SubPathExpr, 325 | } 326 | *volMounts = append(*volMounts, volMount) 327 | } 328 | } 329 | 330 | func getHTTPSVolumeMount(presto *prestodbv1alpha1.Presto, 331 | podSpec *corev1.PodSpec) *corev1.VolumeMount { 332 | httpsSecretVolume := corev1.Volume{ 333 | Name: getHTTPSSecretVolName(presto.Status.Uuid), 334 | VolumeSource: corev1.VolumeSource{ 335 | Secret: &corev1.SecretVolumeSource{ 336 | SecretName: presto.Spec.Coordinator.HttpsKeyPairSecretName, 337 | }, 338 | }, 339 | } 340 | 341 | podSpec.Volumes = append(podSpec.Volumes, httpsSecretVolume) 342 | return &corev1.VolumeMount{ 343 | Name: getHTTPSSecretVolName(presto.Status.Uuid), 344 | ReadOnly: true, 345 | MountPath: httpsVolPath, 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /pkg/controller/presto/presto_properties.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "fmt" 5 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 6 | corev1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/api/resource" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | "strings" 11 | ) 12 | 13 | func buildConfigMap(presto *v1alpha1.Presto, isCoordinator bool, configMapName string, 14 | labels map[string]string) (*corev1.ConfigMap, error) { 15 | nodeProperties := "" 16 | if isCoordinator { 17 | nodeProperties = coordinatorNodePropsMap() 18 | } else { 19 | nodeProperties = workerNodePropsMap() 20 | 21 | } 22 | configProperties := "" 23 | var err error 24 | if isCoordinator { 25 | configProperties, err = coordinatorConfigPropsMap(presto) 26 | } else { 27 | configProperties, err = workerConfigPropsMap(presto) 28 | } 29 | if err != nil { 30 | return nil, err 31 | } 32 | jvmConfig := "" 33 | if isCoordinator { 34 | jvmConfig, err = coordinatorJVMConfigMap(presto) 35 | } else { 36 | jvmConfig, err = workerJVMConfigMap(presto) 37 | } 38 | if err != nil { 39 | return nil, err 40 | } 41 | propertiesFiles := map[string]string{ 42 | nodePropertiesKey: nodeProperties, 43 | configPropertiesKey: configProperties, 44 | jvmConfigKey: jvmConfig, 45 | // added the shutdown script in etc folder to avoid mounting another volume 46 | prestoShutdownScript: strings.ReplaceAll(shutdownScriptContent, "{MOUNT_PATH}", getPrestoPath(presto)), 47 | } 48 | for filename, content := range presto.Spec.AdditionalPrestoPropFiles { 49 | propertiesFiles[filename] = content 50 | } 51 | return &corev1.ConfigMap{ 52 | ObjectMeta: metav1.ObjectMeta{ 53 | Name: configMapName, 54 | Namespace: presto.Namespace, 55 | Labels: labels, 56 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 57 | }, 58 | Data: propertiesFiles, 59 | }, nil 60 | } 61 | 62 | // return createdConfig, error 63 | func createCoordinatorConfig(presto *v1alpha1.Presto, c client.Client, 64 | lbls map[string]string) (bool, error) { 65 | configMapName := getCoordinatorConfigMapName(presto.Status.Uuid) 66 | configMap, err := buildConfigMap(presto, true, configMapName, lbls) 67 | if err != nil { 68 | return false, err 69 | } 70 | return createConfigMap(configMapName, presto, c, configMap, lbls) 71 | } 72 | 73 | func createWorkerConfig(presto *v1alpha1.Presto, c client.Client, 74 | lbls map[string]string) (bool, error) { 75 | configMapName := getWorkerConfigMapName(presto.Status.Uuid) 76 | configMap, err := buildConfigMap(presto, false, configMapName, lbls) 77 | if err != nil { 78 | return false, err 79 | } 80 | return createConfigMap(configMapName, presto, c, configMap, lbls) 81 | } 82 | 83 | func coordinatorNodePropsMap() string { 84 | var sb strings.Builder 85 | sb.WriteString(fmt.Sprintf("node.environment=prestoproduction\n")) 86 | sb.WriteString(fmt.Sprintf("node.data-dir=/data/presto\n")) 87 | return sb.String() 88 | } 89 | 90 | func coordinatorJVMConfigMap(presto *v1alpha1.Presto) (string, error) { 91 | var sb strings.Builder 92 | addDefaultJVMProps(&sb) 93 | memlimit, err := resource.ParseQuantity(presto.Spec.Coordinator.MemoryLimit) 94 | if err != nil { 95 | return "", &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Coordinator.MemoryLimit: " + 96 | "'%v': %v", presto.Spec.Coordinator.MemoryLimit, err)} 97 | } 98 | memoryMb := memlimit.Value()/1024/1024 99 | sb.WriteString(fmt.Sprintf("-Xmx%dm\n", memoryMb)) 100 | // adding JVM config at the end as the right most will take effect 101 | if len(presto.Spec.Coordinator.AdditionalJVMConfig) > 0 { 102 | sb.WriteString(fmt.Sprintf("%s\n", presto.Spec.Coordinator.AdditionalJVMConfig)) 103 | } 104 | 105 | return sb.String(), nil 106 | } 107 | 108 | func coordinatorConfigPropsMap(presto *v1alpha1.Presto) (string, error) { 109 | coordinatorInternalName := getCoordinatorInternalName(presto.Status.Uuid) 110 | systemProps, err := getSystemProps(presto, coordinatorInternalName) 111 | if err != nil { 112 | return "", err 113 | } 114 | var sb strings.Builder 115 | for key, value := range systemProps { 116 | sb.WriteString(fmt.Sprintf("%s=%s\n", key, value)) 117 | } 118 | for key, value := range presto.Spec.Coordinator.AdditionalProps { 119 | if _, ok := systemProps[key]; ok { 120 | return "", &OperatorError{"%s is a system property. Cannot be specified as additional property"} 121 | } else { 122 | sb.WriteString(fmt.Sprintf("%s=%s\n", key, value)) 123 | } 124 | } 125 | return sb.String(), nil 126 | } 127 | 128 | func getHTTPPort(presto *v1alpha1.Presto) (int32, int32) { 129 | port := int32(prestoPort) 130 | if presto.Spec.Service.Port != nil { 131 | port = *presto.Spec.Service.Port 132 | } 133 | var httpport int32 134 | if presto.Spec.Coordinator.HttpsEnabled { 135 | if port == int32(prestoPort) { 136 | // a way to generate another port 137 | httpport = prestoPort + 1 138 | } else { 139 | httpport = int32(prestoPort) 140 | } 141 | return httpport, port 142 | } else{ 143 | httpport = port 144 | return httpport, -1 145 | } 146 | } 147 | 148 | func getSystemProps(presto *v1alpha1.Presto, coordinatorInternalName string) (map[string]string, error) { 149 | httpPort, httpsPort := getHTTPPort(presto) 150 | 151 | var systemProps = make(map[string]string) 152 | if presto.Spec.Coordinator.HttpsEnabled { 153 | if len(presto.Spec.Coordinator.HttpsKeyPairPassword) == 0 { 154 | return nil, &OperatorError{errormsg: "HttpsKeyPairPassword has to be specified when HTTPS is enabled"} 155 | } 156 | if len(presto.Spec.Coordinator.HttpsKeyPairSecretKey) == 0 { 157 | return nil, &OperatorError{errormsg: "HttpsKeyPairSecretKey has to be specified when HTTPS is enabled"} 158 | } 159 | if len(presto.Spec.Coordinator.HttpsKeyPairSecretName) == 0 { 160 | return nil, &OperatorError{errormsg: "HttpsKeyPairSecretName has to be specified when HTTPS is enabled"} 161 | } 162 | systemProps = map[string]string{ 163 | "coordinator": "true", 164 | "node.internal-address": coordinatorInternalName, 165 | "discovery.uri": fmt.Sprintf("http://%s:%d\n", coordinatorInternalName, httpPort), 166 | "node-scheduler.include-coordinator": "false", 167 | "discovery-server.enabled": "true", 168 | "http-server.http.enabled": "true", 169 | "http-server.https.enabled": "true", 170 | "http-server.https.port": fmt.Sprintf("%d", httpsPort), 171 | "http-server.http.port": fmt.Sprintf("%d", httpPort), 172 | "http-server.https.keystore.path": httpsVolPath + "/" + presto.Spec.Coordinator.HttpsKeyPairSecretKey, 173 | "http-server.https.keystore.key": presto.Spec.Coordinator.HttpsKeyPairPassword, 174 | } 175 | } else { 176 | systemProps = map[string]string{ 177 | "coordinator": "true", 178 | "http-server.http.port": fmt.Sprintf("%d", httpPort), 179 | "node.internal-address": coordinatorInternalName, 180 | "discovery.uri": fmt.Sprintf("http://%s:%d\n", coordinatorInternalName, httpPort), 181 | "node-scheduler.include-coordinator": "false", 182 | "discovery-server.enabled": "true", 183 | "http-server.https.enabled": "false", 184 | } 185 | } 186 | return systemProps, nil 187 | } 188 | 189 | func workerNodePropsMap() string { 190 | var sb strings.Builder 191 | sb.WriteString(fmt.Sprintf("node.environment=prestoproduction\n")) 192 | //TODO: data-dir to be made configurable as a persistent volume 193 | sb.WriteString(fmt.Sprintf("node.data-dir=/data/presto\n")) 194 | return sb.String() 195 | } 196 | 197 | func workerJVMConfigMap(presto *v1alpha1.Presto) (string, error) { 198 | var sb strings.Builder 199 | addDefaultJVMProps(&sb) 200 | memlimit, err := resource.ParseQuantity(presto.Spec.Worker.MemoryLimit) 201 | if err != nil { 202 | return "", &OperatorError{fmt.Sprintf("cannot parse presto.Spec.Worker.MemoryLimit: " + 203 | "'%v': %v", presto.Spec.Worker.MemoryLimit, err)} 204 | } 205 | memoryMb := memlimit.Value()/1024/1024 206 | sb.WriteString(fmt.Sprintf("-Xmx%dm\n", memoryMb)) 207 | // adding JVM config at the end as the right most will take effect 208 | if len(presto.Spec.Worker.AdditionalJVMConfig) > 0 { 209 | sb.WriteString(fmt.Sprintf("%s\n", presto.Spec.Worker.AdditionalJVMConfig)) 210 | } 211 | return sb.String(), nil 212 | } 213 | 214 | func addDefaultJVMProps(sb *strings.Builder) { 215 | sb.WriteString(fmt.Sprintf("-server\n")) 216 | sb.WriteString(fmt.Sprintf("-XX:-UseBiasedLocking\n")) 217 | sb.WriteString(fmt.Sprintf("-XX:+UseG1GC\n")) 218 | sb.WriteString(fmt.Sprintf("-XX:G1HeapRegionSize=32M\n")) 219 | sb.WriteString(fmt.Sprintf("-XX:+ExplicitGCInvokesConcurrent\n")) 220 | sb.WriteString(fmt.Sprintf("-XX:+ExitOnOutOfMemoryError\n")) 221 | sb.WriteString(fmt.Sprintf("-XX:+UseGCOverheadLimit\n")) 222 | sb.WriteString(fmt.Sprintf("-XX:+HeapDumpOnOutOfMemoryError\n")) 223 | sb.WriteString(fmt.Sprintf("-XX:ReservedCodeCacheSize=512M\n")) 224 | sb.WriteString(fmt.Sprintf("-Djdk.attach.allowAttachSelf=true\n")) 225 | sb.WriteString(fmt.Sprintf("-Djdk.nio.maxCachedBufferSize=2000000\n")) 226 | } 227 | 228 | func workerConfigPropsMap(presto *v1alpha1.Presto) (string, error) { 229 | httpPort, _ := getHTTPPort(presto) 230 | 231 | var systemProps = map[string]string { 232 | "coordinator": "false", 233 | "http-server.http.port": fmt.Sprintf("%d", prestoPort), 234 | "discovery.uri": fmt.Sprintf("http://%s:%d", getCoordinatorInternalName(presto.Status.Uuid), httpPort), 235 | } 236 | var sb strings.Builder 237 | for key, value := range systemProps { 238 | sb.WriteString(fmt.Sprintf("%s=%s\n", key, value)) 239 | } 240 | for key, value := range presto.Spec.Worker.AdditionalProps { 241 | if _, ok := systemProps[key]; ok { 242 | return "", &OperatorError{"%s is a system property. Cannot be specified as additional property"} 243 | } else { 244 | sb.WriteString(fmt.Sprintf("%s=%s\n", key, value)) 245 | } 246 | } 247 | return sb.String(), nil 248 | } 249 | 250 | func getPropsVolumeMount(presto *v1alpha1.Presto, podSpec *corev1.PodSpec, 251 | isCoordinator bool) *corev1.VolumeMount { 252 | configMap := "" 253 | configMapVol := "" 254 | 255 | if isCoordinator { 256 | configMap = getCoordinatorConfigMapName(presto.Status.Uuid) 257 | configMapVol = getCoordinatorConfigVolumeName(presto.Status.Uuid) 258 | } else { 259 | configMap = getWorkerConfigMapName(presto.Status.Uuid) 260 | configMapVol = getWorkerConfigVolumeName(presto.Status.Uuid) 261 | } 262 | 263 | volumeProjectionsProperties := make([]corev1.VolumeProjection, 1) 264 | volumeProjectionsProperties[0] = corev1.VolumeProjection{ 265 | ConfigMap: &corev1.ConfigMapProjection{ 266 | LocalObjectReference: corev1.LocalObjectReference{ 267 | Name:configMap, 268 | }, 269 | }, 270 | } 271 | 272 | propsVolume := corev1.Volume{ 273 | Name: configMapVol, 274 | VolumeSource: corev1.VolumeSource{ 275 | Projected: &corev1.ProjectedVolumeSource{ 276 | Sources: volumeProjectionsProperties, 277 | }, 278 | }, 279 | } 280 | podSpec.Volumes = append(podSpec.Volumes, propsVolume) 281 | return &corev1.VolumeMount{ 282 | Name: configMapVol, 283 | ReadOnly: true, 284 | MountPath: getPrestoPath(presto), 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /pkg/controller/presto/services.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/prestodb/presto-kubernetes-operator/pkg/apis/prestodb/v1alpha1" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/labels" 10 | "k8s.io/apimachinery/pkg/util/intstr" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | // This function is used to create headless service 15 | // Headless service can allow communication using coordiator name rather than service IP 16 | func createHeadLessPodDiscoverySvc(presto *v1alpha1.Presto, r *ReconcilePresto, 17 | lbls map[string]string) (error, bool) { 18 | created := false 19 | svcKey, svcLabelVal := getPodDiscoveryServiceLabel(presto.Status.Uuid) 20 | lbls[svcKey] = svcLabelVal 21 | service := &corev1.Service{ 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: getPodDiscoveryServiceName(presto.Status.Uuid), 24 | Namespace: presto.Namespace, 25 | Labels: lbls, 26 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 27 | }, 28 | Spec: corev1.ServiceSpec{ 29 | Selector: lbls, 30 | ClusterIP: "None", 31 | }, 32 | } 33 | 34 | oldService := &corev1.ServiceList{} 35 | err := r.client.List(context.Background(), 36 | oldService, 37 | &client.ListOptions{ 38 | Namespace: presto.Namespace, 39 | LabelSelector: labels.SelectorFromSet(labels.Set{svcKey: svcLabelVal}), 40 | }) 41 | if err != nil { 42 | return err, created 43 | } 44 | if len(oldService.Items) == 0 { 45 | createErr := r.client.Create(context.Background(), service) 46 | if createErr != nil { 47 | return createErr, created 48 | } 49 | created = true 50 | } 51 | return nil, created 52 | } 53 | 54 | func createOrGetService(presto *v1alpha1.Presto, r *ReconcilePresto, 55 | lbls map[string]string) (error, *corev1.Service, bool) { 56 | created := false 57 | svcKey, svcLabelVal := getExternalServiceLabel(presto.Status.Uuid) 58 | lbls[svcKey] = svcLabelVal 59 | wk, wv := getCoordinatorPodLabel(presto.Status.Uuid) 60 | if presto.Spec.Service.Type == corev1.ServiceTypeExternalName { 61 | return &OperatorError{fmt.Sprintf("Service of the following type" + 62 | " not supported: %s", corev1.ServiceTypeExternalName) }, nil, created 63 | } 64 | servicePort := getServicePort(presto) 65 | 66 | service := &corev1.Service{ 67 | ObjectMeta: metav1.ObjectMeta{ 68 | Name: getExternalServiceName(presto.Status.Uuid), 69 | Namespace: presto.Namespace, 70 | Labels: lbls, 71 | OwnerReferences: []metav1.OwnerReference{*getOwnerReference(presto)}, 72 | }, 73 | Spec: corev1.ServiceSpec{ 74 | Selector: labels.Set{wk: wv}, 75 | ClusterIP: presto.Spec.Service.ClusterIP, 76 | Type: presto.Spec.Service.Type, 77 | ExternalIPs: presto.Spec.Service.ExternalIPs, 78 | SessionAffinity: presto.Spec.Service.SessionAffinity, 79 | LoadBalancerIP: presto.Spec.Service.LoadBalancerIP, 80 | LoadBalancerSourceRanges: presto.Spec.Service.LoadBalancerSourceRanges, 81 | ExternalName: presto.Spec.Service.ExternalName, 82 | ExternalTrafficPolicy: presto.Spec.Service.ExternalTrafficPolicy, 83 | HealthCheckNodePort: presto.Spec.Service.HealthCheckNodePort, 84 | PublishNotReadyAddresses: presto.Spec.Service.PublishNotReadyAddresses, 85 | SessionAffinityConfig: presto.Spec.Service.SessionAffinityConfig, 86 | IPFamily: presto.Spec.Service.IPFamily, 87 | Ports: servicePort, 88 | }, 89 | } 90 | err, retService := getService(r, presto, labels.Set{svcKey: svcLabelVal}) 91 | if err != nil { 92 | return err, nil, created 93 | } 94 | if retService == nil { 95 | createErr := r.client.Create(context.TODO(), service) 96 | if createErr != nil { 97 | return createErr, nil, created 98 | } 99 | created = true 100 | err, retService = getService(r, presto, lbls) 101 | if err != nil { 102 | return err, nil, created 103 | } 104 | } 105 | return nil, retService, created 106 | } 107 | 108 | func getService(r *ReconcilePresto, presto *v1alpha1.Presto, 109 | lbls map[string]string) (error, *corev1.Service){ 110 | services := &corev1.ServiceList{} 111 | err := r.client.List(context.TODO(), 112 | services, 113 | &client.ListOptions{ 114 | Namespace: presto.Namespace, 115 | LabelSelector: labels.SelectorFromSet(lbls), 116 | }) 117 | if err != nil { 118 | return err, nil 119 | } 120 | if len(services.Items) == 0 { 121 | return nil, nil 122 | } else { 123 | return nil, &services.Items[0] 124 | } 125 | 126 | } 127 | 128 | func getServicePort(presto *v1alpha1.Presto) []corev1.ServicePort { 129 | port := int32(prestoPort) 130 | if presto.Spec.Service.Port != nil { 131 | port = *presto.Spec.Service.Port 132 | } 133 | if presto.Spec.Service.Type != "ClusterIP" && presto.Spec.Service.NodePort != nil { 134 | return []corev1.ServicePort{ 135 | { 136 | Name: "presto-coordinator-port", 137 | Port: port, 138 | NodePort: *presto.Spec.Service.NodePort, 139 | TargetPort: intstr.IntOrString{ 140 | IntVal: port, 141 | }, 142 | }, 143 | } 144 | } 145 | return []corev1.ServicePort{ 146 | { 147 | Name: "presto-coordinator-port", 148 | Port: port, 149 | TargetPort: intstr.IntOrString{ 150 | IntVal: port, 151 | }, 152 | }, 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/controller/presto/watch_predicate.go: -------------------------------------------------------------------------------- 1 | package presto 2 | 3 | import ( 4 | "sigs.k8s.io/controller-runtime/pkg/event" 5 | "sigs.k8s.io/controller-runtime/pkg/predicate" 6 | logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 7 | ) 8 | 9 | type GenerationChangedPredicate struct { 10 | predicate.Funcs 11 | } 12 | 13 | // Predicate to ignore the Update events where the resource version has not been changed 14 | // This is needed to avoid the events generated by the status updates. 15 | // The idea has been picked up from here: 16 | // https://www.openshift.com/blog/kubernetes-operators-best-practices 17 | func (GenerationChangedPredicate) Update(e event.UpdateEvent) bool { 18 | var log = logf.Log.WithName("predicate").WithName("prestoControllerStatusFilter") 19 | if e.MetaOld == nil { 20 | log.Error(nil, "Update event has no old metadata", "event", e) 21 | return false 22 | } 23 | if e.ObjectOld == nil { 24 | log.Error(nil, "Update event has no old runtime object to update", "event", e) 25 | return false 26 | } 27 | if e.ObjectNew == nil { 28 | log.Error(nil, "Update event has no new runtime object for update", "event", e) 29 | return false 30 | } 31 | if e.MetaNew == nil { 32 | log.Error(nil, "Update event has no new metadata", "event", e) 33 | return false 34 | } 35 | if e.MetaNew.GetGeneration() == e.MetaOld.GetGeneration() && e.MetaNew.GetGeneration() != 0 { 36 | return false 37 | } 38 | return true 39 | } -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | // Place any runtime dependencies as imports in this file. 4 | // Go modules will be forced to download and install them. 5 | package tools 6 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | Version = "0.0.1" 5 | ) 6 | --------------------------------------------------------------------------------