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.AvoidLiteralsInIfCondition" })
091    protected void processInput(String line) throws IOException {
092        logger.finer(() -> "vmop agent(in): " + line);
093
094        // Check validity
095        if (line.isEmpty() || !Character.isDigit(line.charAt(0))) {
096            logger.warning(() -> "Illegal vmop agent response: " + line);
097            return;
098        }
099
100        // Check positive responses
101        if (line.startsWith("220 ")) {
102            var evt = new VmopAgentConnected();
103            logger.fine(() -> "Vmop agent triggers " + evt);
104            rep().fire(evt);
105            return;
106        }
107        if (line.startsWith("201 ")) {
108            Event<?> cmd = executing.pop();
109            if (cmd instanceof VmopAgentLogIn login) {
110                var evt = new VmopAgentLoggedIn(login);
111                logger.fine(() -> "Vmop agent triggers " + evt);
112                rep().fire(evt);
113            } else {
114                logger.severe(() -> "Response " + line
115                    + " does not match executing command " + cmd);
116            }
117            return;
118        }
119        if (line.startsWith("202 ")) {
120            Event<?> cmd = executing.pop();
121            if (cmd instanceof VmopAgentLogOut logout) {
122                var evt = new VmopAgentLoggedOut(logout);
123                logger.fine(() -> "Vmop agent triggers " + evt);
124                rep().fire(evt);
125            } else {
126                logger.severe(() -> "Response " + line
127                    + "does not match executing command " + cmd);
128            }
129            return;
130        }
131
132        // Ignore unhandled continuations
133        if (line.charAt(0) == '1') {
134            return;
135        }
136
137        // Error
138        logger.warning(() -> "Error response from vmop agent: " + line);
139        executing.pop();
140    }
141
142}