001/* 002 * VM-Operator 003 * Copyright (C) 2025 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 java.io.IOException; 022import java.nio.file.Path; 023import java.util.List; 024import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent; 025import org.jgrapes.core.Channel; 026import org.jgrapes.core.annotation.Handler; 027 028/** 029 * A component that handles the communication with an agent 030 * running in the VM. 031 * 032 * If the log level for this class is set to fine, the messages 033 * exchanged on the socket are logged. 034 */ 035public abstract class AgentConnector extends QemuConnector { 036 037 protected String channelId; 038 039 /** 040 * Instantiates a new agent connector. 041 * 042 * @param componentChannel the component channel 043 * @throws IOException Signals that an I/O exception has occurred. 044 */ 045 public AgentConnector(Channel componentChannel) throws IOException { 046 super(componentChannel); 047 } 048 049 /** 050 * Extracts the channel id and the socket path from the QEMU 051 * command line. 052 * 053 * @param command the command 054 * @param chardev the chardev 055 */ 056 @SuppressWarnings("PMD.CognitiveComplexity") 057 protected void configureConnection(List<String> command, String chardev) { 058 Path socketPath = null; 059 for (var arg : command) { 060 if (arg.startsWith("virtserialport,") 061 && arg.contains("chardev=" + chardev)) { 062 for (var prop : arg.split(",")) { 063 if (prop.startsWith("id=")) { 064 channelId = prop.substring(3); 065 } 066 } 067 } 068 if (arg.startsWith("socket,") 069 && arg.contains("id=" + chardev)) { 070 for (var prop : arg.split(",")) { 071 if (prop.startsWith("path=")) { 072 socketPath = Path.of(prop.substring(5)); 073 } 074 } 075 } 076 } 077 if (channelId == null || socketPath == null) { 078 logger.warning(() -> "Definition of chardev " + chardev 079 + " missing in runner template."); 080 return; 081 } 082 logger.fine(() -> getClass().getSimpleName() + " configured with" 083 + " channelId=" + channelId); 084 super.configure(socketPath); 085 } 086 087 /** 088 * When the virtual serial port with the configured channel id has 089 * been opened call {@link #agentConnected()}. 090 * 091 * @param event the event 092 */ 093 @Handler 094 public void onVserportChanged(VserportChangeEvent event) { 095 if (event.id().equals(channelId)) { 096 if (event.isOpen()) { 097 agentConnected(); 098 } else { 099 agentDisconnected(); 100 } 101 } 102 } 103 104 /** 105 * Called when the agent in the VM opens the connection. The 106 * default implementation does nothing. 107 */ 108 @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") 109 protected void agentConnected() { 110 // Default is to do nothing. 111 } 112 113 /** 114 * Called when the agent in the VM closes the connection. The 115 * default implementation does nothing. 116 */ 117 @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") 118 protected void agentDisconnected() { 119 // Default is to do nothing. 120 } 121 122}