001/* 002 * VM-Operator 003 * Copyright (C) 2024 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.runner.qemu; 020 021import com.google.gson.JsonObject; 022import io.kubernetes.client.apimachinery.GroupVersionKind; 023import io.kubernetes.client.openapi.ApiException; 024import io.kubernetes.client.openapi.models.EventsV1Event; 025import java.io.IOException; 026import java.util.logging.Level; 027import static org.jdrupes.vmoperator.common.Constants.APP_NAME; 028import org.jdrupes.vmoperator.common.Constants.Crd; 029import org.jdrupes.vmoperator.common.Constants.Status; 030import org.jdrupes.vmoperator.common.K8s; 031import org.jdrupes.vmoperator.common.K8sClient; 032import org.jdrupes.vmoperator.common.VmDefinitionStub; 033import org.jdrupes.vmoperator.runner.qemu.events.Exit; 034import org.jdrupes.vmoperator.runner.qemu.events.SpiceDisconnectedEvent; 035import org.jdrupes.vmoperator.runner.qemu.events.SpiceInitializedEvent; 036import org.jgrapes.core.Channel; 037import org.jgrapes.core.annotation.Handler; 038import org.jgrapes.core.events.Start; 039 040/** 041 * A (sub)component that updates the console status in the CR status. 042 * Created as child of {@link StatusUpdater}. 043 */ 044@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 045public class ConsoleTracker extends VmDefUpdater { 046 047 private VmDefinitionStub vmStub; 048 private String mainChannelClientHost; 049 private long mainChannelClientPort; 050 051 /** 052 * Instantiates a new status updater. 053 * 054 * @param componentChannel the component channel 055 */ 056 @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") 057 public ConsoleTracker(Channel componentChannel) { 058 super(componentChannel); 059 apiClient = (K8sClient) io.kubernetes.client.openapi.Configuration 060 .getDefaultApiClient(); 061 } 062 063 /** 064 * Handle the start event. 065 * 066 * @param event the event 067 * @throws IOException 068 * @throws ApiException 069 */ 070 @Handler 071 public void onStart(Start event) { 072 if (namespace == null) { 073 return; 074 } 075 try { 076 vmStub = VmDefinitionStub.get(apiClient, 077 new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM), 078 namespace, vmName); 079 } catch (ApiException e) { 080 logger.log(Level.SEVERE, e, 081 () -> "Cannot access VM object, terminating."); 082 event.cancel(true); 083 fire(new Exit(1)); 084 } 085 } 086 087 /** 088 * On spice connected. 089 * 090 * @param event the event 091 * @throws ApiException the api exception 092 */ 093 @Handler 094 @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition", 095 "PMD.AvoidDuplicateLiterals" }) 096 public void onSpiceInitialized(SpiceInitializedEvent event) 097 throws ApiException { 098 if (vmStub == null) { 099 return; 100 } 101 102 // Only process connections using main channel. 103 if (event.channelType() != 1) { 104 return; 105 } 106 mainChannelClientHost = event.clientHost(); 107 mainChannelClientPort = event.clientPort(); 108 vmStub.updateStatus(from -> { 109 JsonObject status = updateCondition(from, "ConsoleConnected", true, 110 "Connected", "Connection from " + event.clientHost()); 111 status.addProperty(Status.CONSOLE_CLIENT, event.clientHost()); 112 return status; 113 }); 114 115 // Log event 116 var evt = new EventsV1Event() 117 .reportingController(Crd.GROUP + "/" + APP_NAME) 118 .action("ConsoleConnectionUpdate") 119 .reason("Connection from " + event.clientHost()); 120 K8s.createEvent(apiClient, vmStub.model().get(), evt); 121 } 122 123 /** 124 * On spice disconnected. 125 * 126 * @param event the event 127 * @throws ApiException the api exception 128 */ 129 @Handler 130 @SuppressWarnings("PMD.AvoidDuplicateLiterals") 131 public void onSpiceDisconnected(SpiceDisconnectedEvent event) 132 throws ApiException { 133 if (vmStub == null) { 134 return; 135 } 136 137 // Only process disconnects from main channel. 138 if (!event.clientHost().equals(mainChannelClientHost) 139 || event.clientPort() != mainChannelClientPort) { 140 return; 141 } 142 vmStub.updateStatus(from -> { 143 JsonObject status = updateCondition(from, "ConsoleConnected", false, 144 "Disconnected", event.clientHost() + " has disconnected"); 145 status.addProperty(Status.CONSOLE_CLIENT, ""); 146 return status; 147 }); 148 149 // Log event 150 var evt = new EventsV1Event() 151 .reportingController(Crd.GROUP + "/" + APP_NAME) 152 .action("ConsoleConnectionUpdate") 153 .reason("Disconnected from " + event.clientHost()); 154 K8s.createEvent(apiClient, vmStub.model().get(), evt); 155 } 156}