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 logger.fine(() -> "vmop agent(out): login " + event.user()); 063 if (writer().isPresent()) { 064 executing.add(event); 065 sendCommand("login " + event.user()); 066 } 067 } 068 069 /** 070 * On vmop agent logout. 071 * 072 * @param event the event 073 * @throws IOException Signals that an I/O exception has occurred. 074 */ 075 @Handler 076 public void onVmopAgentLogout(VmopAgentLogOut event) throws IOException { 077 logger.fine(() -> "vmop agent(out): logout"); 078 if (writer().isPresent()) { 079 executing.add(event); 080 sendCommand("logout"); 081 } 082 } 083 084 @Override 085 @SuppressWarnings({ "PMD.UnnecessaryReturn", 086 "PMD.AvoidLiteralsInIfCondition" }) 087 protected void processInput(String line) throws IOException { 088 logger.fine(() -> "vmop agent(in): " + line); 089 090 // Check validity 091 if (line.isEmpty() || !Character.isDigit(line.charAt(0))) { 092 logger.warning(() -> "Illegal response: " + line); 093 return; 094 } 095 096 // Check positive responses 097 if (line.startsWith("220 ")) { 098 rep().fire(new VmopAgentConnected()); 099 return; 100 } 101 if (line.startsWith("201 ")) { 102 Event<?> cmd = executing.pop(); 103 if (cmd instanceof VmopAgentLogIn login) { 104 rep().fire(new VmopAgentLoggedIn(login)); 105 } else { 106 logger.severe(() -> "Response " + line 107 + " does not match executing command " + cmd); 108 } 109 return; 110 } 111 if (line.startsWith("202 ")) { 112 Event<?> cmd = executing.pop(); 113 if (cmd instanceof VmopAgentLogOut logout) { 114 rep().fire(new VmopAgentLoggedOut(logout)); 115 } else { 116 logger.severe(() -> "Response " + line 117 + "does not match executing command " + cmd); 118 } 119 return; 120 } 121 122 // Ignore unhandled continuations 123 if (line.charAt(0) == '1') { 124 return; 125 } 126 127 // Error 128 logger.warning(() -> "Error response: " + line); 129 executing.pop(); 130 } 131 132}