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}