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 */ 044public class ConsoleTracker extends VmDefUpdater { 045 046 private VmDefinitionStub vmStub; 047 private String mainChannelClientHost; 048 private long mainChannelClientPort; 049 050 /** 051 * Instantiates a new status updater. 052 * 053 * @param componentChannel the component channel 054 */ 055 public ConsoleTracker(Channel componentChannel) { 056 super(componentChannel); 057 apiClient = (K8sClient) io.kubernetes.client.openapi.Configuration 058 .getDefaultApiClient(); 059 } 060 061 /** 062 * Handle the start event. 063 * 064 * @param event the event 065 * @throws IOException 066 * @throws ApiException 067 */ 068 @Handler 069 public void onStart(Start event) { 070 if (namespace == null) { 071 return; 072 } 073 try { 074 vmStub = VmDefinitionStub.get(apiClient, 075 new GroupVersionKind(Crd.GROUP, "", Crd.KIND_VM), 076 namespace, vmName); 077 } catch (ApiException e) { 078 logger.log(Level.SEVERE, e, 079 () -> "Cannot access VM object, terminating."); 080 event.cancel(true); 081 fire(new Exit(1)); 082 } 083 } 084 085 /** 086 * On spice connected. 087 * 088 * @param event the event 089 * @throws ApiException the api exception 090 */ 091 @Handler 092 @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition" }) 093 public void onSpiceInitialized(SpiceInitializedEvent event) 094 throws ApiException { 095 if (vmStub == null) { 096 return; 097 } 098 099 // Only process connections using main channel. 100 if (event.channelType() != 1) { 101 return; 102 } 103 mainChannelClientHost = event.clientHost(); 104 mainChannelClientPort = event.clientPort(); 105 vmStub.updateStatus(from -> { 106 JsonObject status = updateCondition(from, "ConsoleConnected", true, 107 "Connected", "Connection from " + event.clientHost()); 108 status.addProperty(Status.CONSOLE_CLIENT, event.clientHost()); 109 return status; 110 }); 111 112 // Log event 113 var evt = new EventsV1Event() 114 .reportingController(Crd.GROUP + "/" + APP_NAME) 115 .action("ConsoleConnectionUpdate") 116 .reason("Connection from " + event.clientHost()); 117 K8s.createEvent(apiClient, vmStub.model().get(), evt); 118 } 119 120 /** 121 * On spice disconnected. 122 * 123 * @param event the event 124 * @throws ApiException the api exception 125 */ 126 @Handler 127 public void onSpiceDisconnected(SpiceDisconnectedEvent event) 128 throws ApiException { 129 if (vmStub == null) { 130 return; 131 } 132 133 // Only process disconnects from main channel. 134 if (!event.clientHost().equals(mainChannelClientHost) 135 || event.clientPort() != mainChannelClientPort) { 136 return; 137 } 138 vmStub.updateStatus(from -> { 139 JsonObject status = updateCondition(from, "ConsoleConnected", false, 140 "Disconnected", event.clientHost() + " has disconnected"); 141 status.addProperty(Status.CONSOLE_CLIENT, ""); 142 return status; 143 }); 144 145 // Log event 146 var evt = new EventsV1Event() 147 .reportingController(Crd.GROUP + "/" + APP_NAME) 148 .action("ConsoleConnectionUpdate") 149 .reason("Disconnected from " + event.clientHost()); 150 K8s.createEvent(apiClient, vmStub.model().get(), evt); 151 } 152}