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}