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