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.common; 020 021import io.kubernetes.client.util.Strings; 022import java.net.InetAddress; 023import java.net.UnknownHostException; 024import java.util.Collections; 025import java.util.List; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031/** 032 * Represents internally used dynamic data associated with a 033 * {@link VmDefinition}. 034 */ 035public class VmExtraData { 036 037 private static final Logger logger 038 = Logger.getLogger(VmExtraData.class.getName()); 039 040 private final VmDefinition vmDef; 041 private String nodeName = ""; 042 private List<String> nodeAddresses = Collections.emptyList(); 043 private long resetCount; 044 045 /** 046 * Initializes a new instance. 047 * 048 * @param vmDef the VM definition 049 */ 050 public VmExtraData(VmDefinition vmDef) { 051 this.vmDef = vmDef; 052 vmDef.extra(this); 053 } 054 055 /** 056 * Sets the node info. 057 * 058 * @param name the name 059 * @param addresses the addresses 060 * @return the VM extra data 061 */ 062 public VmExtraData nodeInfo(String name, List<String> addresses) { 063 nodeName = name; 064 nodeAddresses = addresses; 065 return this; 066 } 067 068 /** 069 * Return the node name. 070 * 071 * @return the string 072 */ 073 public String nodeName() { 074 return nodeName; 075 } 076 077 /** 078 * Gets the node addresses. 079 * 080 * @return the nodeAddresses 081 */ 082 public List<String> nodeAddresses() { 083 return nodeAddresses; 084 } 085 086 /** 087 * Sets the reset count. 088 * 089 * @param resetCount the reset count 090 * @return the vm extra data 091 */ 092 public VmExtraData resetCount(long resetCount) { 093 this.resetCount = resetCount; 094 return this; 095 } 096 097 /** 098 * Returns the reset count. 099 * 100 * @return the long 101 */ 102 public long resetCount() { 103 return resetCount; 104 } 105 106 /** 107 * Create a connection file. 108 * 109 * @param password the password 110 * @param preferredIpVersion the preferred IP version 111 * @param deleteConnectionFile the delete connection file 112 * @return the string 113 */ 114 public Optional<String> connectionFile(String password, 115 Class<?> preferredIpVersion, boolean deleteConnectionFile) { 116 var addr = displayIp(preferredIpVersion); 117 if (addr.isEmpty()) { 118 logger 119 .severe(() -> "Failed to find display IP for " + vmDef.name()); 120 return Optional.empty(); 121 } 122 var port = vmDef.<Number> fromVm("display", "spice", "port") 123 .map(Number::longValue); 124 if (port.isEmpty()) { 125 logger 126 .severe(() -> "No port defined for display of " + vmDef.name()); 127 return Optional.empty(); 128 } 129 StringBuffer data = new StringBuffer(100) 130 .append("[virt-viewer]\ntype=spice\nhost=") 131 .append(addr.get().getHostAddress()).append("\nport=") 132 .append(port.get().toString()) 133 .append('\n'); 134 if (password != null) { 135 data.append("password=").append(password).append('\n'); 136 } 137 vmDef.<String> fromVm("display", "spice", "proxyUrl") 138 .ifPresent(u -> { 139 if (!Strings.isNullOrEmpty(u)) { 140 data.append("proxy=").append(u).append('\n'); 141 } 142 }); 143 if (deleteConnectionFile) { 144 data.append("delete-this-file=1\n"); 145 } 146 return Optional.of(data.toString()); 147 } 148 149 private Optional<InetAddress> displayIp(Class<?> preferredIpVersion) { 150 Optional<String> server = vmDef.fromVm("display", "spice", "server"); 151 if (server.isPresent()) { 152 var srv = server.get(); 153 try { 154 var addr = InetAddress.getByName(srv); 155 logger.fine(() -> "Using IP address from CRD for " 156 + vmDef.metadata().getName() + ": " + addr); 157 return Optional.of(addr); 158 } catch (UnknownHostException e) { 159 logger.log(Level.SEVERE, e, () -> "Invalid server address " 160 + srv + ": " + e.getMessage()); 161 return Optional.empty(); 162 } 163 } 164 var addrs = nodeAddresses.stream().map(a -> { 165 try { 166 return InetAddress.getByName(a); 167 } catch (UnknownHostException e) { 168 logger.warning(() -> "Invalid IP address: " + a); 169 return null; 170 } 171 }).filter(Objects::nonNull).toList(); 172 logger.fine( 173 () -> "Known IP addresses for " + vmDef.name() + ": " + addrs); 174 return addrs.stream() 175 .filter(a -> preferredIpVersion.isAssignableFrom(a.getClass())) 176 .findFirst().or(() -> addrs.stream().findFirst()); 177 } 178 179}