001/* 002 * VM-Operator 003 * Copyright (C) 2024 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 java.time.Duration; 022import java.time.Instant; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Set; 028import java.util.function.Function; 029import java.util.stream.Collectors; 030import org.jdrupes.vmoperator.common.VmDefinition.Grant; 031import org.jdrupes.vmoperator.common.VmDefinition.Permission; 032import org.jdrupes.vmoperator.util.DataPath; 033 034/** 035 * Represents a VM pool. 036 */ 037@SuppressWarnings({ "PMD.DataClass" }) 038public class VmPool { 039 040 private String name; 041 private String retention; 042 private boolean defined; 043 private List<Grant> permissions = Collections.emptyList(); 044 private final Set<String> vms 045 = Collections.synchronizedSet(new HashSet<>()); 046 047 /** 048 * Instantiates a new vm pool. 049 * 050 * @param name the name 051 */ 052 public VmPool(String name) { 053 this.name = name; 054 } 055 056 /** 057 * Returns the name. 058 * 059 * @return the name 060 */ 061 public String name() { 062 return name; 063 } 064 065 /** 066 * Sets the name. 067 * 068 * @param name the name to set 069 */ 070 public void setName(String name) { 071 this.name = name; 072 } 073 074 /** 075 * Checks if is defined. 076 * 077 * @return the result 078 */ 079 public boolean isDefined() { 080 return defined; 081 } 082 083 /** 084 * Sets if is. 085 * 086 * @param defined the defined to set 087 */ 088 public void setDefined(boolean defined) { 089 this.defined = defined; 090 } 091 092 /** 093 * Gets the retention. 094 * 095 * @return the retention 096 */ 097 public String retention() { 098 return retention; 099 } 100 101 /** 102 * Sets the retention. 103 * 104 * @param retention the retention to set 105 */ 106 public void setRetention(String retention) { 107 this.retention = retention; 108 } 109 110 /** 111 * Permissions granted for a VM from the pool. 112 * 113 * @return the permissions 114 */ 115 public List<Grant> permissions() { 116 return permissions; 117 } 118 119 /** 120 * Sets the permissions. 121 * 122 * @param permissions the permissions to set 123 */ 124 public void setPermissions(List<Grant> permissions) { 125 this.permissions = permissions; 126 } 127 128 /** 129 * Returns the VM names. 130 * 131 * @return the vms 132 */ 133 public Set<String> vms() { 134 return vms; 135 } 136 137 /** 138 * Collect all permissions for the given user with the given roles. 139 * 140 * @param user the user 141 * @param roles the roles 142 * @return the sets the 143 */ 144 public Set<Permission> permissionsFor(String user, 145 Collection<String> roles) { 146 return permissions.stream() 147 .filter(g -> DataPath.get(g, "user").map(u -> u.equals(user)) 148 .orElse(false) 149 || DataPath.get(g, "role").map(roles::contains).orElse(false)) 150 .map(g -> DataPath.<Set<Permission>> get(g, "may") 151 .orElse(Collections.emptySet()).stream()) 152 .flatMap(Function.identity()).collect(Collectors.toSet()); 153 } 154 155 /** 156 * Checks if the given VM belongs to the pool and is not in use. 157 * 158 * @param vmDef the vm def 159 * @return true, if is assignable 160 */ 161 @SuppressWarnings("PMD.SimplifyBooleanReturns") 162 public boolean isAssignable(VmDefinition vmDef) { 163 // Check if the VM is in the pool 164 if (!vmDef.pools().contains(name)) { 165 return false; 166 } 167 168 // Check if the VM is not in use 169 if (vmDef.consoleConnected()) { 170 return false; 171 } 172 173 // If not assigned, it's usable 174 if (vmDef.assignedTo().isEmpty()) { 175 return true; 176 } 177 178 // Check if it is to be retained 179 if (vmDef.assignmentLastUsed() 180 .map(this::retainUntil) 181 .map(ru -> Instant.now().isBefore(ru)).orElse(false)) { 182 return false; 183 } 184 185 // Additional check in case lastUsed has not been updated 186 // by PoolMonitor#onVmDefChanged() yet ("race condition") 187 if (vmDef.condition("ConsoleConnected") 188 .map(cc -> cc.getLastTransitionTime().toInstant()) 189 .map(this::retainUntil) 190 .map(ru -> Instant.now().isBefore(ru)).orElse(false)) { 191 return false; 192 } 193 return true; 194 } 195 196 /** 197 * Return the instant until which an assignment should be retained. 198 * 199 * @param lastUsed the last used 200 * @return the instant 201 */ 202 public Instant retainUntil(Instant lastUsed) { 203 if (retention.startsWith("P")) { 204 return lastUsed.plus(Duration.parse(retention)); 205 } 206 return Instant.parse(retention); 207 } 208 209 /** 210 * To string. 211 * 212 * @return the string 213 */ 214 @Override 215 @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition", 216 "PMD.AvoidSynchronizedStatement" }) 217 public String toString() { 218 StringBuilder builder = new StringBuilder(50); 219 builder.append("VmPool [name=").append(name).append(", permissions=") 220 .append(permissions).append(", vms="); 221 if (vms.size() <= 3) { 222 builder.append(vms); 223 } else { 224 synchronized (vms) { 225 builder.append('[').append(vms.stream().limit(3) 226 .map(s -> s + ",").collect(Collectors.joining())) 227 .append("...]"); 228 } 229 } 230 builder.append(']'); 231 return builder.toString(); 232 } 233}