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