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}