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.util.Deque; 023import java.util.concurrent.ConcurrentLinkedDeque; 024import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected; 025import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn; 026import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogOut; 027import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn; 028import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedOut; 029import org.jgrapes.core.Channel; 030import org.jgrapes.core.Event; 031import org.jgrapes.core.annotation.Handler; 032 033/** 034 * A component that handles the communication over the vmop agent 035 * socket. 036 * 037 * If the log level for this class is set to fine, the messages 038 * exchanged on the socket are logged. 039 */ 040public class VmopAgentClient extends AgentConnector { 041 042 private final Deque<Event<?>> executing = new ConcurrentLinkedDeque<>(); 043 044 /** 045 * Instantiates a new VM operator agent client. 046 * 047 * @param componentChannel the component channel 048 * @throws IOException Signals that an I/O exception has occurred. 049 */ 050 public VmopAgentClient(Channel componentChannel) throws IOException { 051 super(componentChannel); 052 } 053 054 /** 055 * On vmop agent login. 056 * 057 * @param event the event 058 * @throws IOException Signals that an I/O exception has occurred. 059 */ 060 @Handler 061 public void onVmopAgentLogIn(VmopAgentLogIn event) throws IOException { 062 if (writer().isPresent()) { 063 logger.fine(() -> "Vmop agent handles:" + event); 064 executing.add(event); 065 logger.finer(() -> "vmop agent(out): login " + event.user()); 066 sendCommand("login " + event.user()); 067 } else { 068 logger 069 .warning(() -> "No vmop agent connection for sending " + event); 070 } 071 } 072 073 /** 074 * On vmop agent logout. 075 * 076 * @param event the event 077 * @throws IOException Signals that an I/O exception has occurred. 078 */ 079 @Handler 080 public void onVmopAgentLogout(VmopAgentLogOut event) throws IOException { 081 if (writer().isPresent()) { 082 logger.fine(() -> "Vmop agent handles:" + event); 083 executing.add(event); 084 logger.finer(() -> "vmop agent(out): logout"); 085 sendCommand("logout"); 086 } 087 } 088 089 @Override 090 @SuppressWarnings({ "PMD.UnnecessaryReturn", 091 "PMD.AvoidLiteralsInIfCondition" }) 092 protected void processInput(String line) throws IOException { 093 logger.finer(() -> "vmop agent(in): " + line); 094 095 // Check validity 096 if (line.isEmpty() || !Character.isDigit(line.charAt(0))) { 097 logger.warning(() -> "Illegal vmop agent response: " + line); 098 return; 099 } 100 101 // Check positive responses 102 if (line.startsWith("220 ")) { 103 var evt = new VmopAgentConnected(); 104 logger.fine(() -> "Vmop agent triggers " + evt); 105 rep().fire(evt); 106 return; 107 } 108 if (line.startsWith("201 ")) { 109 Event<?> cmd = executing.pop(); 110 if (cmd instanceof VmopAgentLogIn login) { 111 var evt = new VmopAgentLoggedIn(login); 112 logger.fine(() -> "Vmop agent triggers " + evt); 113 rep().fire(evt); 114 } else { 115 logger.severe(() -> "Response " + line 116 + " does not match executing command " + cmd); 117 } 118 return; 119 } 120 if (line.startsWith("202 ")) { 121 Event<?> cmd = executing.pop(); 122 if (cmd instanceof VmopAgentLogOut logout) { 123 var evt = new VmopAgentLoggedOut(logout); 124 logger.fine(() -> "Vmop agent triggers " + evt); 125 rep().fire(evt); 126 } else { 127 logger.severe(() -> "Response " + line 128 + "does not match executing command " + cmd); 129 } 130 return; 131 } 132 133 // Ignore unhandled continuations 134 if (line.charAt(0) == '1') { 135 return; 136 } 137 138 // Error 139 logger.warning(() -> "Error response from vmop agent: " + line); 140 executing.pop(); 141 } 142 143}