From 9cbe8b2782c79c0abe3a4ef7aa5edf252f24d749 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Tue, 1 Jul 2025 22:08:31 +0000 Subject: [PATCH] feat: complete the first poc of the operator --- deploy/examples/sample-aiprovider-ollama.yaml | 18 ++ deploy/examples/sample-podmortem.yaml | 14 + pom.xml | 10 +- .../reconcile/PodmortemReconciler.java | 103 -------- .../operator/service/AIInterfaceClient.java | 30 +++ .../service/AIInterfaceRestClient.java | 25 ++ .../operator/service/LogParserClient.java | 39 +++ .../operator/service/LogParserRestClient.java | 24 ++ .../reconcile/config/PodmortemReconciler.java | 247 ++++++++++++++++++ .../kubernetes/ai-interface-deployment.yaml | 23 ++ src/main/kubernetes/ai-interface-service.yaml | 13 + src/main/kubernetes/aiprovider-crd.yaml | 86 ++++++ ...nifest.yaml => log-parser-deployment.yaml} | 13 - src/main/kubernetes/log-parser-service.yaml | 13 + src/main/kubernetes/podmortem-crd.yaml | 92 +++++++ src/main/resources/application.properties | 7 + 16 files changed, 640 insertions(+), 117 deletions(-) create mode 100644 deploy/examples/sample-aiprovider-ollama.yaml create mode 100644 deploy/examples/sample-podmortem.yaml delete mode 100644 src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java create mode 100644 src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java create mode 100644 src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java create mode 100644 src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java create mode 100644 src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java create mode 100644 src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java create mode 100644 src/main/kubernetes/ai-interface-deployment.yaml create mode 100644 src/main/kubernetes/ai-interface-service.yaml create mode 100644 src/main/kubernetes/aiprovider-crd.yaml rename src/main/kubernetes/{log-parser-manifest.yaml => log-parser-deployment.yaml} (65%) create mode 100644 src/main/kubernetes/log-parser-service.yaml create mode 100644 src/main/kubernetes/podmortem-crd.yaml diff --git a/deploy/examples/sample-aiprovider-ollama.yaml b/deploy/examples/sample-aiprovider-ollama.yaml new file mode 100644 index 0000000..85887af --- /dev/null +++ b/deploy/examples/sample-aiprovider-ollama.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: podmortem.redhat.com/v1alpha1 +kind: AIProvider +metadata: + name: ollama-local + namespace: podmortem-system +spec: + providerId: "ollama" + apiUrl: "http://ollama-service.ai-models.svc.cluster.local:11434" + modelId: "mistral:7b" + timeoutSeconds: 60 + maxRetries: 2 + cachingEnabled: true + temperature: 0.3 + maxTokens: 500 + additionalConfig: + stream: "false" + num_predict: "500" diff --git a/deploy/examples/sample-podmortem.yaml b/deploy/examples/sample-podmortem.yaml new file mode 100644 index 0000000..946017c --- /dev/null +++ b/deploy/examples/sample-podmortem.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: podmortem.redhat.com/v1alpha1 +kind: Podmortem +metadata: + name: monitor-my-apps + namespace: default +spec: + podSelector: + matchLabels: + app: "my-application" + aiProviderRef: + name: "ollama-local" + namespace: "podmortem-system" + aiAnalysisEnabled: true diff --git a/pom.xml b/pom.xml index de43291..c185f4d 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ true 3.5.3 - 1.0-f094e1c-SNAPSHOT + 1.0-1db4e13-SNAPSHOT @@ -68,6 +68,14 @@ io.quarkus quarkus-kubernetes-client + + io.quarkus + quarkus-rest-client + + + io.quarkus + quarkus-rest-client-jackson + com.redhat.podmortem common diff --git a/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java b/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java deleted file mode 100644 index 558835d..0000000 --- a/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.redhat.podmortem.operator.reconcile; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData; -import com.redhat.podmortem.common.model.kube.podmortem.Podmortem; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.events.v1.Event; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ControllerConfiguration -public class PodmortemReconciler implements Reconciler { - - private static final Logger log = LoggerFactory.getLogger(PodmortemReconciler.class); - private final KubernetesClient client; - private final ObjectMapper objectMapper; - - public PodmortemReconciler(KubernetesClient client) { - this.client = client; - this.objectMapper = new ObjectMapper(); - this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - } - - @Override - public UpdateControl reconcile(Podmortem resource, Context context) { - log.info("Reconciling PodmortemConfig: {}", resource.getMetadata().getName()); - - List pods = - client.pods() - .inAnyNamespace() - .withLabelSelector(resource.getSpec().getPodSelector()) - .list() - .getItems(); - - for (Pod pod : pods) { - pod.getStatus() - .getContainerStatuses() - .forEach( - containerStatus -> { - if (containerStatus.getState().getTerminated() != null - && containerStatus.getState().getTerminated().getExitCode() - != 0) { - - log.info( - "Pod has a container with an ungraceful exit", - "pod", - pod.getMetadata().getName(), - "container", - containerStatus.getName(), - "exitCode", - containerStatus - .getState() - .getTerminated() - .getExitCode()); - - // get Logs - String podLogs = - client.pods() - .inNamespace(pod.getMetadata().getNamespace()) - .withName(pod.getMetadata().getName()) - .getLog(); - - // get Events for the Pod - List events = - client.events() - .v1() - .events() - .inNamespace(pod.getMetadata().getNamespace()) - .withField( - "involvedObject.name", - pod.getMetadata().getName()) - .list() - .getItems(); - - // assemble the failure data - PodFailureData failureData = - new PodFailureData(pod, podLogs, events); - - // marshal data to JSON and log it - try { - String jsonData = - objectMapper.writeValueAsString(failureData); - log.info("Collected pod failure data:\n{}", jsonData); - } catch (JsonProcessingException e) { - log.error( - "Failed to marshal failure data to JSON for pod {}", - pod.getMetadata().getName(), - e); - } - } - }); - } - return UpdateControl.noUpdate(); - } -} diff --git a/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java new file mode 100644 index 0000000..56fd24d --- /dev/null +++ b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java @@ -0,0 +1,30 @@ +package com.redhat.podmortem.operator.service; + +import com.redhat.podmortem.common.model.analysis.AnalysisResult; +import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider; +import com.redhat.podmortem.common.model.provider.AIResponse; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class AIInterfaceClient { + + private static final Logger log = LoggerFactory.getLogger(AIInterfaceClient.class); + + @Inject @RestClient AIInterfaceRestClient restClient; + + public Uni generateExplanation( + AnalysisResult analysisResult, AIProvider aiProvider) { + log.debug("Sending AI explanation request for analysis"); + return restClient + .generateExplanation(analysisResult, aiProvider) + .onItem() + .invoke(response -> log.debug("Received AI explanation response")) + .onFailure() + .invoke(throwable -> log.error("AI explanation generation failed", throwable)); + } +} diff --git a/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java new file mode 100644 index 0000000..8e277d7 --- /dev/null +++ b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java @@ -0,0 +1,25 @@ +package com.redhat.podmortem.operator.service; + +import com.redhat.podmortem.common.model.analysis.AnalysisResult; +import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider; +import com.redhat.podmortem.common.model.provider.AIResponse; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@ApplicationScoped +@RegisterRestClient(configKey = "ai-interface") +@Path("/api/v1") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public interface AIInterfaceRestClient { + + @POST + @Path("/analyze") + Uni generateExplanation(AnalysisResult analysisResult, AIProvider aiProvider); +} diff --git a/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java b/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java new file mode 100644 index 0000000..291513f --- /dev/null +++ b/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java @@ -0,0 +1,39 @@ +package com.redhat.podmortem.operator.service; + +import com.redhat.podmortem.common.model.analysis.AnalysisResult; +import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class LogParserClient { + + private static final Logger log = LoggerFactory.getLogger(LogParserClient.class); + + @Inject @RestClient LogParserRestClient restClient; + + public Uni analyzeLog(PodFailureData failureData) { + log.debug( + "Sending log analysis request for pod: {}", + failureData.getPod().getMetadata().getName()); + return restClient + .parseLogs(failureData) + .onItem() + .invoke( + result -> + log.debug( + "Received analysis result for pod: {}", + failureData.getPod().getMetadata().getName())) + .onFailure() + .invoke( + throwable -> + log.error( + "Log analysis failed for pod: {}", + failureData.getPod().getMetadata().getName(), + throwable)); + } +} diff --git a/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java b/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java new file mode 100644 index 0000000..c4174bb --- /dev/null +++ b/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java @@ -0,0 +1,24 @@ +package com.redhat.podmortem.operator.service; + +import com.redhat.podmortem.common.model.analysis.AnalysisResult; +import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@ApplicationScoped +@RegisterRestClient(configKey = "log-parser") +@Path("/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public interface LogParserRestClient { + + @POST + @Path("/parse") + Uni parseLogs(PodFailureData failureData); +} diff --git a/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java b/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java new file mode 100644 index 0000000..a12e947 --- /dev/null +++ b/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java @@ -0,0 +1,247 @@ +package com.redhat.podmortem.reconcile.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.redhat.podmortem.common.model.analysis.AnalysisResult; +import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider; +import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData; +import com.redhat.podmortem.common.model.kube.podmortem.Podmortem; +import com.redhat.podmortem.common.model.kube.podmortem.PodmortemStatus; +import com.redhat.podmortem.operator.service.AIInterfaceClient; +import com.redhat.podmortem.operator.service.LogParserClient; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.events.v1.Event; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ControllerConfiguration +@ApplicationScoped +public class PodmortemReconciler implements Reconciler { + + private static final Logger log = LoggerFactory.getLogger(PodmortemReconciler.class); + + @Inject KubernetesClient client; + + @Inject LogParserClient logParserClient; + + @Inject AIInterfaceClient aiInterfaceClient; + + private final ObjectMapper objectMapper; + + public PodmortemReconciler() { + this.objectMapper = new ObjectMapper(); + this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + } + + @Override + public UpdateControl reconcile(Podmortem resource, Context context) { + log.info("Reconciling Podmortem: {}", resource.getMetadata().getName()); + + // Initialize status if not present + if (resource.getStatus() == null) { + resource.setStatus(new PodmortemStatus()); + } + + try { + // Find pods matching the selector + List pods = findMatchingPods(resource); + + // Process each pod for failures + for (Pod pod : pods) { + if (hasPodFailed(pod)) { + processPodFailure(resource, pod); + } + } + + // Update status + updatePodmortemStatus(resource, "Ready", "Monitoring pods for failures"); + + return UpdateControl.patchStatus(resource); + + } catch (Exception e) { + log.error("Error reconciling Podmortem: {}", resource.getMetadata().getName(), e); + updatePodmortemStatus(resource, "Error", "Failed to reconcile: " + e.getMessage()); + return UpdateControl.patchStatus(resource); + } + } + + private List findMatchingPods(Podmortem resource) { + return client.pods() + .inAnyNamespace() + .withLabelSelector(resource.getSpec().getPodSelector()) + .list() + .getItems(); + } + + private boolean hasPodFailed(Pod pod) { + return pod.getStatus().getContainerStatuses().stream() + .anyMatch( + containerStatus -> + containerStatus.getState().getTerminated() != null + && containerStatus.getState().getTerminated().getExitCode() + != 0); + } + + private void processPodFailure(Podmortem resource, Pod pod) { + log.info("Processing pod failure for pod: {}", pod.getMetadata().getName()); + + try { + // Collect failure data + PodFailureData failureData = collectPodFailureData(pod); + + // Send to log parser for analysis + logParserClient + .analyzeLog(failureData) + .subscribe() + .with( + analysisResult -> { + log.info( + "Log analysis completed for pod: {}", + pod.getMetadata().getName()); + handleAnalysisResult(resource, pod, analysisResult); + }, + failure -> { + log.error( + "Log analysis failed for pod: {}", + pod.getMetadata().getName(), + failure); + updatePodFailureStatus( + resource, pod, "Analysis failed: " + failure.getMessage()); + }); + + } catch (Exception e) { + log.error("Error processing pod failure for pod: {}", pod.getMetadata().getName(), e); + updatePodFailureStatus(resource, pod, "Processing failed: " + e.getMessage()); + } + } + + private PodFailureData collectPodFailureData(Pod pod) { + // Get pod logs + String podLogs = + client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .getLog(); + + // Get events for the pod + List events = + client.events() + .v1() + .events() + .inNamespace(pod.getMetadata().getNamespace()) + .withField("involvedObject.name", pod.getMetadata().getName()) + .list() + .getItems(); + + return new PodFailureData(pod, podLogs, events); + } + + private void handleAnalysisResult(Podmortem resource, Pod pod, AnalysisResult analysisResult) { + log.info("Handling analysis result for pod: {}", pod.getMetadata().getName()); + + // Check if AI analysis is enabled and AI provider is configured + if (Boolean.TRUE.equals(resource.getSpec().getAiAnalysisEnabled()) + && resource.getSpec().getAiProviderRef() != null) { + + // Get AI provider + Optional aiProvider = getAIProvider(resource); + + if (aiProvider.isPresent()) { + // Send to AI interface for explanation + aiInterfaceClient + .generateExplanation(analysisResult, aiProvider.get()) + .subscribe() + .with( + aiResponse -> { + log.info( + "AI analysis completed for pod: {}", + pod.getMetadata().getName()); + updatePodFailureStatus( + resource, + pod, + "Analysis completed with AI explanation: " + + aiResponse.getExplanation()); + }, + failure -> { + log.error( + "AI analysis failed for pod: {}", + pod.getMetadata().getName(), + failure); + updatePodFailureStatus( + resource, + pod, + "Pattern analysis completed, AI analysis failed: " + + failure.getMessage()); + }); + } else { + log.warn( + "AI provider not found for Podmortem: {}", + resource.getMetadata().getName()); + updatePodFailureStatus( + resource, pod, "Pattern analysis completed, AI provider not found"); + } + } else { + // Only pattern analysis, no AI + updatePodFailureStatus(resource, pod, "Pattern analysis completed"); + } + } + + private Optional getAIProvider(Podmortem resource) { + if (resource.getSpec().getAiProviderRef() == null) { + return Optional.empty(); + } + + String providerName = resource.getSpec().getAiProviderRef().getName(); + String providerNamespace = resource.getSpec().getAiProviderRef().getNamespace(); + + // Default to same namespace if not specified + if (providerNamespace == null) { + providerNamespace = resource.getMetadata().getNamespace(); + } + + try { + AIProvider aiProvider = + client.resources(AIProvider.class) + .inNamespace(providerNamespace) + .withName(providerName) + .get(); + + return Optional.ofNullable(aiProvider); + } catch (Exception e) { + log.error("Error fetching AI provider: {}/{}", providerNamespace, providerName, e); + return Optional.empty(); + } + } + + private void updatePodmortemStatus(Podmortem resource, String phase, String message) { + PodmortemStatus status = resource.getStatus(); + if (status == null) { + status = new PodmortemStatus(); + resource.setStatus(status); + } + + status.setPhase(phase); + status.setMessage(message); + status.setLastUpdate(Instant.now()); + } + + private void updatePodFailureStatus(Podmortem resource, Pod pod, String message) { + log.info("Updated status for pod {}: {}", pod.getMetadata().getName(), message); + // In a real implementation, you might want to store per-pod failure status + // For now, we'll just update the overall status + updatePodmortemStatus( + resource, + "Processing", + String.format("Pod %s: %s", pod.getMetadata().getName(), message)); + } +} diff --git a/src/main/kubernetes/ai-interface-deployment.yaml b/src/main/kubernetes/ai-interface-deployment.yaml new file mode 100644 index 0000000..1061f6e --- /dev/null +++ b/src/main/kubernetes/ai-interface-deployment.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: podmortem-ai-interface-service +spec: + replicas: 1 + selector: + matchLabels: + app: podmortem-ai-interface-service + template: + metadata: + labels: + app: podmortem-ai-interface-service + spec: + containers: + - name: ai-interface + image: ghcr.io/podmortem/podmortem-ai-interface:latest + ports: + - containerPort: 8080 + env: + - name: QUARKUS_LOG_LEVEL + value: "INFO" diff --git a/src/main/kubernetes/ai-interface-service.yaml b/src/main/kubernetes/ai-interface-service.yaml new file mode 100644 index 0000000..15e5138 --- /dev/null +++ b/src/main/kubernetes/ai-interface-service.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: podmortem-ai-interface-service +spec: + selector: + app: podmortem-ai-interface-service + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + type: ClusterIP diff --git a/src/main/kubernetes/aiprovider-crd.yaml b/src/main/kubernetes/aiprovider-crd.yaml new file mode 100644 index 0000000..08f9562 --- /dev/null +++ b/src/main/kubernetes/aiprovider-crd.yaml @@ -0,0 +1,86 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: aiproviders.podmortem.redhat.com +spec: + group: podmortem.redhat.com + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + providerId: + type: string + description: "Unique identifier for the AI provider (e.g., 'openai', 'ollama')" + apiUrl: + type: string + description: "Base URL for the AI provider API" + modelId: + type: string + description: "Model identifier to use for inference" + authenticationRef: + type: object + properties: + secretName: + type: string + secretKey: + type: string + description: "Reference to Kubernetes Secret containing authentication credentials" + timeoutSeconds: + type: integer + default: 30 + description: "Request timeout in seconds" + maxRetries: + type: integer + default: 3 + description: "Maximum number of retry attempts" + cachingEnabled: + type: boolean + default: true + description: "Enable response caching for similar requests" + promptTemplate: + type: string + description: "Custom prompt template to use" + maxTokens: + type: integer + default: 500 + description: "Maximum number of tokens to generate" + temperature: + type: number + default: 0.3 + description: "Temperature for AI generation" + additionalConfig: + type: object + additionalProperties: + type: string + description: "Provider-specific additional configuration" + status: + type: object + properties: + phase: + type: string + enum: ["Pending", "Ready", "Failed"] + message: + type: string + lastValidated: + type: string + format: date-time + observedGeneration: + type: integer + format: int64 + subresources: + status: {} + scope: Namespaced + names: + plural: aiproviders + singular: aiprovider + kind: AIProvider + shortNames: + - aip diff --git a/src/main/kubernetes/log-parser-manifest.yaml b/src/main/kubernetes/log-parser-deployment.yaml similarity index 65% rename from src/main/kubernetes/log-parser-manifest.yaml rename to src/main/kubernetes/log-parser-deployment.yaml index b029049..6036229 100644 --- a/src/main/kubernetes/log-parser-manifest.yaml +++ b/src/main/kubernetes/log-parser-deployment.yaml @@ -18,16 +18,3 @@ spec: image: ghcr.io/podmortem/podmortem-log-parser:latest ports: - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - name: podmortem-log-parser-service -spec: - selector: - app: podmortem-log-parser-service - ports: - - protocol: TCP - port: 8080 - targetPort: 8080 - type: ClusterIP \ No newline at end of file diff --git a/src/main/kubernetes/log-parser-service.yaml b/src/main/kubernetes/log-parser-service.yaml new file mode 100644 index 0000000..fd963e1 --- /dev/null +++ b/src/main/kubernetes/log-parser-service.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: podmortem-log-parser-service +spec: + selector: + app: podmortem-log-parser-service + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + type: ClusterIP diff --git a/src/main/kubernetes/podmortem-crd.yaml b/src/main/kubernetes/podmortem-crd.yaml new file mode 100644 index 0000000..73c046c --- /dev/null +++ b/src/main/kubernetes/podmortem-crd.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: podmortems.podmortem.redhat.com +spec: + group: podmortem.redhat.com + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + podSelector: + type: object + properties: + matchLabels: + type: object + additionalProperties: + type: string + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + values: + type: array + items: + type: string + description: "Label selector for pods to monitor" + aiProviderRef: + type: object + properties: + name: + type: string + description: "Name of the AIProvider resource to use for analysis" + namespace: + type: string + description: "Namespace of the AIProvider resource (optional, defaults to same namespace)" + description: "Reference to an AI provider for generating explanations" + aiAnalysisEnabled: + type: boolean + default: true + description: "Enable AI-powered analysis for this pod failure" + status: + type: object + properties: + phase: + type: string + enum: ["Pending", "Ready", "Processing", "Error"] + message: + type: string + lastUpdate: + type: string + format: date-time + observedGeneration: + type: integer + format: int64 + recentFailures: + type: array + items: + type: object + properties: + podName: + type: string + podNamespace: + type: string + failureTime: + type: string + format: date-time + analysisStatus: + type: string + explanation: + type: string + subresources: + status: {} + scope: Namespaced + names: + plural: podmortems + singular: podmortem + kind: Podmortem + shortNames: + - pm diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b9c83b5..8b8d62c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,3 +8,10 @@ quarkus.kubernetes.deployment-target=openshift quarkus.container-image.build=true quarkus.container-image.group=com.redhat.podmortem quarkus.container-image.name=podmortem-operator + +## REST Client Configuration ## +# Log Parser Service +quarkus.rest-client.log-parser.url=http://podmortem-log-parser-service:8080 + +# AI Interface Service +quarkus.rest-client.ai-interface.url=http://podmortem-ai-interface-service:8080