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 static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; 029import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; 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(VM_OP_GROUP, "", VM_OP_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 = from.status(); 110 status.addProperty("consoleClient", event.clientHost()); 111 updateCondition(from, status, "ConsoleConnected", true, "Connected", 112 "Connection from " + event.clientHost()); 113 return status; 114 }); 115 116 // Log event 117 var evt = new EventsV1Event() 118 .reportingController(VM_OP_GROUP + "/" + APP_NAME) 119 .action("ConsoleConnectionUpdate") 120 .reason("Connection from " + event.clientHost()); 121 K8s.createEvent(apiClient, vmStub.model().get(), evt); 122 } 123 124 /** 125 * On spice disconnected. 126 * 127 * @param event the event 128 * @throws ApiException the api exception 129 */ 130 @Handler 131 @SuppressWarnings("PMD.AvoidDuplicateLiterals") 132 public void onSpiceDisconnected(SpiceDisconnectedEvent event) 133 throws ApiException { 134 if (vmStub == null) { 135 return; 136 } 137 138 // Only process disconnects from main channel. 139 if (!event.clientHost().equals(mainChannelClientHost) 140 || event.clientPort() != mainChannelClientPort) { 141 return; 142 } 143 vmStub.updateStatus(from -> { 144 JsonObject status = from.status(); 145 status.addProperty("consoleClient", ""); 146 updateCondition(from, status, "ConsoleConnected", false, 147 "Disconnected", event.clientHost() + " has disconnected"); 148 return status; 149 }); 150 151 // Log event 152 var evt = new EventsV1Event() 153 .reportingController(VM_OP_GROUP + "/" + APP_NAME) 154 .action("ConsoleConnectionUpdate") 155 .reason("Disconnected from " + event.clientHost()); 156 K8s.createEvent(apiClient, vmStub.model().get(), evt); 157 } 158}