From 9f7b1bfcda25eaaeccfe2019e3dd9303f8e618f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Phil=20H=C3=B6fer?= <phil.hoefer@suma-ev.de>
Date: Thu, 6 Jun 2024 10:33:29 +0000
Subject: [PATCH] Fix Deployment

---
 .gitlab-ci.yml                             |  2 +-
 chart/.helmignore                          | 23 +++++++
 chart/Chart.yaml                           | 24 +++++++
 chart/templates/NOTES.txt                  | 16 +++++
 chart/templates/_helpers.tpl               | 51 ++++++++++++++
 chart/templates/deployment.yaml            | 78 ++++++++++++++++++++++
 chart/templates/pdb.yaml                   |  9 +++
 chart/templates/pvc.yaml                   | 11 +++
 chart/templates/service.yaml               | 15 +++++
 chart/templates/tests/test-connection.yaml | 15 +++++
 chart/values.yaml                          | 56 ++++++++++++++++
 data/data.csv                              |  2 +
 src/main.rs                                | 31 +++++----
 13 files changed, 319 insertions(+), 14 deletions(-)
 create mode 100644 chart/.helmignore
 create mode 100644 chart/Chart.yaml
 create mode 100644 chart/templates/NOTES.txt
 create mode 100644 chart/templates/_helpers.tpl
 create mode 100644 chart/templates/deployment.yaml
 create mode 100644 chart/templates/pdb.yaml
 create mode 100644 chart/templates/pvc.yaml
 create mode 100644 chart/templates/service.yaml
 create mode 100644 chart/templates/tests/test-connection.yaml
 create mode 100644 chart/values.yaml
 create mode 100644 data/data.csv

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index db60256..3560cb7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,7 +29,7 @@ deploy:
   stage: deploy
   image: ${KUBERNETES_DEPLOY_IMAGE}
   before_script:
-    - kubectl config use-context open-source/metager-keymanager:gitlab-agent
+    - kubectl config use-context open-source/suggestible:gitlab-agent
   script: |
     helm -n ${KUBERNETES_NAMESPACE} upgrade --install ${HELM_RELEASE_NAME} chart/ \
       --set image.repository=${CI_REGISTRY_IMAGE}/${DOCKER_IMAGE_NAME} \
diff --git a/chart/.helmignore b/chart/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/chart/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/chart/Chart.yaml b/chart/Chart.yaml
new file mode 100644
index 0000000..b8edb26
--- /dev/null
+++ b/chart/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: suggestible
+description: A Helm chart for Suggestible
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.0.0"
diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt
new file mode 100644
index 0000000..4d71236
--- /dev/null
+++ b/chart/templates/NOTES.txt
@@ -0,0 +1,16 @@
+1. Get the application URL by running these commands:
+{{- if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "chart.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "chart.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl
new file mode 100644
index 0000000..fe9065c
--- /dev/null
+++ b/chart/templates/_helpers.tpl
@@ -0,0 +1,51 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "chart.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "chart.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" $name .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "chart.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "chart.labels" -}}
+helm.sh/chart: {{ include "chart.chart" . }}
+{{ include "chart.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "chart.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "chart.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml
new file mode 100644
index 0000000..4e6d4eb
--- /dev/null
+++ b/chart/templates/deployment.yaml
@@ -0,0 +1,78 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "chart.fullname" . }}
+  labels:
+    {{- include "chart.labels" . | nindent 4 }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      {{- include "chart.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "chart.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      volumes:
+        - name: data
+          persistentVolumeClaim:
+            claimName: {{ include "chart.fullname" . }}-data
+      {{- if .Values.application.secretName }}
+        - name: application-secret
+          secret:
+            secretName: {{ .Values.application.secretName }}
+            defaultMode: 420
+      {{- end }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          volumeMounts:
+            - name: data
+              readOnly: false
+              mountPath: /data
+          {{- if .Values.application.secretName }}
+            - name: application-secret
+              readOnly: true
+              mountPath: /app/config/production.json
+              subPath: production.json
+          {{- end }}
+          ports:
+            - name: http
+              containerPort: 8000
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /healthz
+              port: http
+          readinessProbe:
+            httpGet:
+              path: /healthz
+              port: http
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/chart/templates/pdb.yaml b/chart/templates/pdb.yaml
new file mode 100644
index 0000000..4e4dc0a
--- /dev/null
+++ b/chart/templates/pdb.yaml
@@ -0,0 +1,9 @@
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ include "chart.fullname" . }}
+spec:
+  minAvailable: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: {{ .Chart.Name }}
diff --git a/chart/templates/pvc.yaml b/chart/templates/pvc.yaml
new file mode 100644
index 0000000..9b43dab
--- /dev/null
+++ b/chart/templates/pvc.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: {{ include "chart.fullname" . }}-data
+spec:
+  accessModes:
+    - ReadWriteMany
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 20Gi
\ No newline at end of file
diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml
new file mode 100644
index 0000000..dfc5b3a
--- /dev/null
+++ b/chart/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "chart.fullname" . }}
+  labels:
+    {{- include "chart.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "chart.selectorLabels" . | nindent 4 }}
diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..8dfed87
--- /dev/null
+++ b/chart/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "chart.fullname" . }}-test-connection"
+  labels:
+    {{- include "chart.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "chart.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/chart/values.yaml b/chart/values.yaml
new file mode 100644
index 0000000..262355d
--- /dev/null
+++ b/chart/values.yaml
@@ -0,0 +1,56 @@
+# Default values for chart.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: nginx
+  pullPolicy: Always
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: ""
+
+imagePullSecrets: {}
+nameOverride: ""
+fullnameOverride: ""
+application:
+  secretName: ""
+
+podAnnotations: {}
+
+podSecurityContext:
+  runAsUser: 1000
+  runAsGroup: 1000
+  fsGroup: 1000
+
+securityContext:
+  {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP
+  port: 80
+
+resources:
+  {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
diff --git a/data/data.csv b/data/data.csv
new file mode 100644
index 0000000..2b8b012
--- /dev/null
+++ b/data/data.csv
@@ -0,0 +1,2 @@
+"time","referer","request_time","focus","locale","query"
+2024-04-01 08:35:42.000 +0200,,,web,en,what is love?
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index e3ce906..6d27848 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,12 +19,14 @@ use url::Url;
 
 fn main() -> Result<(), io::Error> {
 
-    let markov_chain = build_markov_chain("data.csv")?;
+    let markov_chain = build_markov_chain("../../data/data.csv")
+                        .unwrap_or(build_markov_chain("data.csv")
+                            .unwrap_or_default());
     let filtered_markov_chain = filter_markov_chain(&markov_chain,1);
  
     // Print the Markov Chain for verification
     for (key, values) in &markov_chain {
-        // println!("{}: {:?}", key, values);
+         //println!("{}: {:?}", key, values);
     }
 
     // Test the function
@@ -37,24 +39,25 @@ fn main() -> Result<(), io::Error> {
 
 
 
-    let server = Server::http("0.0.0.0:80").unwrap();
+    let server = Server::http("0.0.0.0:8000").unwrap();
 
     for request in server.incoming_requests() {
-        //  println!("received request! method: {:?}, url: {:?}, headers: {:?}",
-        //      request.method(),
-        //      request.url(),
-        //      request.headers()
-        //  );
+        //   println!("received request! method: {:?}, url: {:?}, headers: {:?}",
+        //       request.method(),
+        //       request.url(),
+        //       request.headers()
+        //   );
         let query = get_query(request.url());
+        //println!("got query:{}", query.clone().unwrap());
         match query {
             Ok(query) => {
                 let prediction = predictn(&filtered_markov_chain, &query,5);
-                println!("Query: {}, Prediction:{}", query, prediction);
+                //println!("Query: {}, Prediction:{}", query, prediction);
                 let response = Response::from_string(prediction);
                 request.respond(response);
             },
             Err(e) => {
-                println!("Error: {}",e);
+                //println!("Error: {}",e);
             }
         }
     }
@@ -63,9 +66,11 @@ fn main() -> Result<(), io::Error> {
 }
 
 fn get_query(request_url: &str) -> Result<String, url::ParseError> {
-    let parsed_url = request_url.strip_prefix("/?").unwrap_or(request_url);
+    let parsed_url = request_url.split_once('?').map_or(request_url, |(_, after)| after);
+    //println!("parsed_url:{}", parsed_url);
     let query_pairs = url::form_urlencoded::parse(parsed_url.as_bytes());
     for (key, value) in query_pairs {
+        //println!("key:{}, value: {}", key, value);
         if key == "q" {
             return Ok(value.into_owned());
         }
@@ -135,7 +140,7 @@ fn get_top_following_words(
 }
 
 fn predict(markov_chain: &MarkovChain, query: &str) -> String {
-    if let Some(top_words) = get_top_following_words(markov_chain, query, 1) {
+    if let Some(top_words) = get_top_following_words(markov_chain, query.split_whitespace().last().unwrap_or(""), 1) {
         if let Some((predicted_word, _)) = top_words.first() {
             return format!("{} {}", query, predicted_word);
         }
@@ -144,7 +149,7 @@ fn predict(markov_chain: &MarkovChain, query: &str) -> String {
 }
 
 fn predictn(markov_chain: &MarkovChain, query: &str, n: usize) -> String {
-    if let Some(top_words) = get_top_following_words(markov_chain, query, n) {
+    if let Some(top_words) = get_top_following_words(markov_chain, query.split_whitespace().last().unwrap_or(""), n) {
         let predictions: Vec<String> = top_words.into_iter()
             .map(|(word, _)| format!("\"{} {}\"",query, word))
             .collect();
-- 
GitLab