diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-12-04 22:03:18 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-04 22:03:18 +0000 |
| commit | b88f07ebb056293b1a29f24e4f42203c09bcbcf8 (patch) | |
| tree | 52e2de58503714cc6510db614b4a654af2fdda3c | |
| parent | 2ca72c0b62e08efd244eba1723bc4fc65d30eed2 (diff) | |
| parent | 4dfae28c5bd656806a7baf7855c95770f4ad0ed8 (diff) | |
merge: Clean up compute service (v1)
This pull request is one in a series of changes that attempt to clean up the
warts of the OpenDC Compute Service. These changes should make the
API more robust for future developments.
## Implementation Notes :hammer_and_pick:
* Do not suspend in compute API
* Expose state directly to clients
* Do not split interface and implementation
## External Dependencies :four_leaf_clover:
* N/A
## Breaking API Changes :warning:
* The methods of `ComputeClient` do not have the suspend modifier anymore.
* Changes to the classes of `ComputeClient` are now immediately visible on the server side.
It is not necessary to call `refresh` anymore.
* `ComputeService` must be constructed via its builder class now.
54 files changed, 1452 insertions, 1518 deletions
diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt index 577fbc73..c26d0b8b 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt @@ -31,14 +31,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Flavor]s accessible by the requesting user. */ - public suspend fun queryFlavors(): List<Flavor> + public fun queryFlavors(): List<Flavor> /** * Obtain a [Flavor] by its unique identifier. * * @param id The identifier of the flavor. */ - public suspend fun findFlavor(id: UUID): Flavor? + public fun findFlavor(id: UUID): Flavor? /** * Create a new [Flavor] instance at this compute service. @@ -49,7 +49,7 @@ public interface ComputeClient : AutoCloseable { * @param labels The identifying labels of the image. * @param meta The non-identifying meta-data of the image. */ - public suspend fun newFlavor( + public fun newFlavor( name: String, cpuCount: Int, memorySize: Long, @@ -60,14 +60,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Image]s accessible by the requesting user. */ - public suspend fun queryImages(): List<Image> + public fun queryImages(): List<Image> /** * Obtain a [Image] by its unique identifier. * * @param id The identifier of the image. */ - public suspend fun findImage(id: UUID): Image? + public fun findImage(id: UUID): Image? /** * Create a new [Image] instance at this compute service. @@ -76,7 +76,7 @@ public interface ComputeClient : AutoCloseable { * @param labels The identifying labels of the image. * @param meta The non-identifying meta-data of the image. */ - public suspend fun newImage( + public fun newImage( name: String, labels: Map<String, String> = emptyMap(), meta: Map<String, Any> = emptyMap() @@ -85,14 +85,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Server]s accessible by the requesting user. */ - public suspend fun queryServers(): List<Server> + public fun queryServers(): List<Server> /** * Obtain a [Server] by its unique identifier. * * @param id The identifier of the server. */ - public suspend fun findServer(id: UUID): Server? + public fun findServer(id: UUID): Server? /** * Create a new [Server] instance at this compute service. @@ -104,7 +104,7 @@ public interface ComputeClient : AutoCloseable { * @param meta The non-identifying meta-data of the server. * @param start A flag to indicate that the server should be started immediately. */ - public suspend fun newServer( + public fun newServer( name: String, image: Image, flavor: Flavor, diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt index 5f511f91..d76e0fba 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt @@ -36,9 +36,4 @@ public interface Flavor : Resource { * The amount of RAM available to the server (in MB). */ public val memorySize: Long - - /** - * Delete the flavor instance. - */ - public suspend fun delete() } diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt index 83e63b81..c4a04b96 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt @@ -25,9 +25,4 @@ package org.opendc.compute.api /** * An image containing a bootable operating system that can directly be executed by physical or virtual server. */ -public interface Image : Resource { - /** - * Delete the image instance. - */ - public suspend fun delete() -} +public interface Image : Resource diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt index 08120848..58082130 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt @@ -49,7 +49,12 @@ public interface Resource { public val meta: Map<String, Any> /** - * Refresh the local state of the resource. + * Reload the attributes of the resource. */ - public suspend fun refresh() + public fun reload() + + /** + * Delete the resource. + */ + public fun delete() } diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt index 64b73d0b..b4cc5129 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt @@ -50,27 +50,13 @@ public interface Server : Resource { /** * Request the server to be started. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * started. */ - public suspend fun start() + public fun start() /** * Request the server to be stopped. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * stopped. - */ - public suspend fun stop() - - /** - * Request the server to be deleted. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * deleted. */ - public suspend fun delete() + public fun stop() /** * Register the specified [ServerWatcher] to watch the state of the server. diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt index 48a17b30..cf995fc3 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt @@ -29,9 +29,6 @@ public interface ServerWatcher { /** * This method is invoked when the state of a [Server] changes. * - * Note that the state of [server] might not reflect the state as reported by the invocation, as a call to - * [Server.refresh] is required to update its state. - * * @param server The server whose state has changed. * @param newState The new state of the server. */ diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java new file mode 100644 index 00000000..eda9a79f --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SplittableRandom; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.opendc.common.Dispatcher; +import org.opendc.common.util.Pacer; +import org.opendc.compute.api.ComputeClient; +import org.opendc.compute.api.Flavor; +import org.opendc.compute.api.Image; +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; +import org.opendc.compute.service.driver.Host; +import org.opendc.compute.service.driver.HostListener; +import org.opendc.compute.service.driver.HostModel; +import org.opendc.compute.service.driver.HostState; +import org.opendc.compute.service.scheduler.ComputeScheduler; +import org.opendc.compute.service.telemetry.SchedulerStats; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ComputeService} hosts the API implementation of the OpenDC Compute Engine. + */ +public final class ComputeService implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(ComputeService.class); + + /** + * The {@link InstantSource} representing the clock tracking the (simulation) time. + */ + private final InstantSource clock; + + /** + * The {@link ComputeScheduler} responsible for placing the servers onto hosts. + */ + private final ComputeScheduler scheduler; + + /** + * The {@link Pacer} used to pace the scheduling requests. + */ + private final Pacer pacer; + + /** + * The {@link SplittableRandom} used to generate the unique identifiers for the service resources. + */ + private final SplittableRandom random = new SplittableRandom(0); + + /** + * A flag to indicate that the service is closed. + */ + private boolean isClosed; + + /** + * A mapping from host to host view. + */ + private final Map<Host, HostView> hostToView = new HashMap<>(); + + /** + * The available hypervisors. + */ + private final Set<HostView> availableHosts = new HashSet<>(); + + /** + * The servers that should be launched by the service. + */ + private final Deque<SchedulingRequest> queue = new ArrayDeque<>(); + + /** + * The active servers in the system. + */ + private final Map<Server, Host> activeServers = new HashMap<>(); + + /** + * The registered flavors for this compute service. + */ + private final Map<UUID, ServiceFlavor> flavorById = new HashMap<>(); + + private final List<ServiceFlavor> flavors = new ArrayList<>(); + + /** + * The registered images for this compute service. + */ + private final Map<UUID, ServiceImage> imageById = new HashMap<>(); + + private final List<ServiceImage> images = new ArrayList<>(); + + /** + * The registered servers for this compute service. + */ + private final Map<UUID, ServiceServer> serverById = new HashMap<>(); + + private final List<ServiceServer> servers = new ArrayList<>(); + + /** + * A [HostListener] used to track the active servers. + */ + private final HostListener hostListener = new HostListener() { + @Override + public void onStateChanged(@NotNull Host host, @NotNull HostState newState) { + LOGGER.debug("Host {} state changed: {}", host, newState); + + final HostView hv = hostToView.get(host); + + if (hv != null) { + if (newState == HostState.UP) { + availableHosts.add(hv); + } else { + availableHosts.remove(hv); + } + } + + // Re-schedule on the new machine + requestSchedulingCycle(); + } + + @Override + public void onStateChanged(@NotNull Host host, @NotNull Server server, @NotNull ServerState newState) { + final ServiceServer serviceServer = (ServiceServer) server; + + if (serviceServer.getHost() != host) { + // This can happen when a server is rescheduled and started on another machine, while being deleted from + // the old machine. + return; + } + + serviceServer.setState(newState); + + if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { + LOGGER.info("Server {} {} {} finished", server.getUid(), server.getName(), server.getFlavor()); + + if (activeServers.remove(server) != null) { + serversActive--; + } + + HostView hv = hostToView.get(host); + final ServiceFlavor flavor = serviceServer.getFlavor(); + if (hv != null) { + hv.provisionedCores -= flavor.getCpuCount(); + hv.instanceCount--; + hv.availableMemory += flavor.getMemorySize(); + } else { + LOGGER.error("Unknown host {}", host); + } + + // Try to reschedule if needed + requestSchedulingCycle(); + } + } + }; + + private int maxCores = 0; + private long maxMemory = 0L; + private long attemptsSuccess = 0L; + private long attemptsFailure = 0L; + private long attemptsError = 0L; + private int serversPending = 0; + private int serversActive = 0; + + /** + * Construct a {@link ComputeService} instance. + */ + ComputeService(Dispatcher dispatcher, ComputeScheduler scheduler, Duration quantum) { + this.clock = dispatcher.getTimeSource(); + this.scheduler = scheduler; + this.pacer = new Pacer(dispatcher, quantum.toMillis(), (time) -> doSchedule()); + } + + /** + * Create a new {@link Builder} instance. + */ + public static Builder builder(Dispatcher dispatcher, ComputeScheduler scheduler) { + return new Builder(dispatcher, scheduler); + } + + /** + * Create a new {@link ComputeClient} to control the compute service. + */ + public ComputeClient newClient() { + if (isClosed) { + throw new IllegalStateException("Service is closed"); + } + return new Client(this); + } + + /** + * Return the {@link Server}s hosted by this service. + */ + public List<Server> getServers() { + return Collections.unmodifiableList(servers); + } + + /** + * Add a {@link Host} to the scheduling pool of the compute service. + */ + public void addHost(Host host) { + // Check if host is already known + if (hostToView.containsKey(host)) { + return; + } + + HostView hv = new HostView(host); + HostModel model = host.getModel(); + + maxCores = Math.max(maxCores, model.cpuCount()); + maxMemory = Math.max(maxMemory, model.memoryCapacity()); + hostToView.put(host, hv); + + if (host.getState() == HostState.UP) { + availableHosts.add(hv); + } + + scheduler.addHost(hv); + host.addListener(hostListener); + } + + /** + * Remove a {@link Host} from the scheduling pool of the compute service. + */ + public void removeHost(Host host) { + HostView view = hostToView.remove(host); + if (view != null) { + availableHosts.remove(view); + scheduler.removeHost(view); + host.removeListener(hostListener); + } + } + + /** + * Lookup the {@link Host} that currently hosts the specified {@link Server}. + */ + public Host lookupHost(Server server) { + if (server instanceof ServiceServer) { + return ((ServiceServer) server).getHost(); + } + + ServiceServer internal = + Objects.requireNonNull(serverById.get(server.getUid()), "Invalid server passed to lookupHost"); + return internal.getHost(); + } + + /** + * Return the {@link Host}s that are registered with this service. + */ + public Set<Host> getHosts() { + return Collections.unmodifiableSet(hostToView.keySet()); + } + + /** + * Collect the statistics about the scheduler component of this service. + */ + public SchedulerStats getSchedulerStats() { + return new SchedulerStats( + availableHosts.size(), + hostToView.size() - availableHosts.size(), + attemptsSuccess, + attemptsFailure, + attemptsError, + servers.size(), + serversPending, + serversActive); + } + + @Override + public void close() { + if (isClosed) { + return; + } + + isClosed = true; + pacer.cancel(); + } + + /** + * Enqueue the specified [server] to be scheduled onto a host. + */ + SchedulingRequest schedule(ServiceServer server) { + LOGGER.debug("Enqueueing server {} to be assigned to host", server.getUid()); + + long now = clock.millis(); + SchedulingRequest request = new SchedulingRequest(server, now); + + server.launchedAt = Instant.ofEpochMilli(now); + queue.add(request); + serversPending++; + requestSchedulingCycle(); + return request; + } + + void delete(ServiceFlavor flavor) { + flavorById.remove(flavor.getUid()); + flavors.remove(flavor); + } + + void delete(ServiceImage image) { + imageById.remove(image.getUid()); + images.remove(image); + } + + void delete(ServiceServer server) { + serverById.remove(server.getUid()); + servers.remove(server); + } + + /** + * Indicate that a new scheduling cycle is needed due to a change to the service's state. + */ + private void requestSchedulingCycle() { + // Bail out in case the queue is empty. + if (queue.isEmpty()) { + return; + } + + pacer.enqueue(); + } + + /** + * Run a single scheduling iteration. + */ + private void doSchedule() { + while (!queue.isEmpty()) { + SchedulingRequest request = queue.peek(); + + if (request.isCancelled) { + queue.poll(); + serversPending--; + continue; + } + + final ServiceServer server = request.server; + final ServiceFlavor flavor = server.getFlavor(); + final HostView hv = scheduler.select(request.server); + + if (hv == null || !hv.getHost().canFit(server)) { + LOGGER.trace( + "Server {} selected for scheduling but no capacity available for it at the moment", server); + + if (flavor.getMemorySize() > maxMemory || flavor.getCpuCount() > maxCores) { + // Remove the incoming image + queue.poll(); + serversPending--; + attemptsFailure++; + + LOGGER.warn("Failed to spawn {}: does not fit", server); + + server.setState(ServerState.TERMINATED); + continue; + } else { + break; + } + } + + Host host = hv.getHost(); + + // Remove request from queue + queue.poll(); + serversPending--; + + LOGGER.info("Assigned server {} to host {}", server, host); + + try { + server.host = host; + + host.spawn(server); + host.start(server); + + serversActive++; + attemptsSuccess++; + + hv.instanceCount++; + hv.provisionedCores += flavor.getCpuCount(); + hv.availableMemory -= flavor.getMemorySize(); + + activeServers.put(server, host); + } catch (Exception cause) { + LOGGER.error("Failed to deploy VM", cause); + attemptsError++; + } + } + } + + /** + * Builder class for a {@link ComputeService}. + */ + public static class Builder { + private final Dispatcher dispatcher; + private final ComputeScheduler computeScheduler; + private Duration quantum = Duration.ofMinutes(5); + + Builder(Dispatcher dispatcher, ComputeScheduler computeScheduler) { + this.dispatcher = dispatcher; + this.computeScheduler = computeScheduler; + } + + /** + * Set the scheduling quantum of the service. + */ + public Builder withQuantum(Duration quantum) { + this.quantum = quantum; + return this; + } + + /** + * Build a {@link ComputeService}. + */ + public ComputeService build() { + return new ComputeService(dispatcher, computeScheduler, quantum); + } + } + + /** + * Implementation of {@link ComputeClient} using a {@link ComputeService}. + */ + private static class Client implements ComputeClient { + private final ComputeService service; + private boolean isClosed; + + Client(ComputeService service) { + this.service = service; + } + + /** + * Method to check if the client is still open and throw an exception if it is not. + */ + private void checkOpen() { + if (isClosed) { + throw new IllegalStateException("Client is already closed"); + } + } + + @NotNull + @Override + public List<Flavor> queryFlavors() { + checkOpen(); + return new ArrayList<>(service.flavors); + } + + @Override + public Flavor findFlavor(@NotNull UUID id) { + checkOpen(); + + return service.flavorById.get(id); + } + + @NotNull + @Override + public Flavor newFlavor( + @NotNull String name, + int cpuCount, + long memorySize, + @NotNull Map<String, String> labels, + @NotNull Map<String, ?> meta) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + ServiceFlavor flavor = new ServiceFlavor(service, uid, name, cpuCount, memorySize, labels, meta); + + service.flavorById.put(uid, flavor); + service.flavors.add(flavor); + + return flavor; + } + + @NotNull + @Override + public List<Image> queryImages() { + checkOpen(); + + return new ArrayList<>(service.images); + } + + @Override + public Image findImage(@NotNull UUID id) { + checkOpen(); + + return service.imageById.get(id); + } + + @NotNull + public Image newImage(@NotNull String name, @NotNull Map<String, String> labels, @NotNull Map<String, ?> meta) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + + ServiceImage image = new ServiceImage(service, uid, name, labels, meta); + + service.imageById.put(uid, image); + service.images.add(image); + + return image; + } + + @NotNull + @Override + public Server newServer( + @NotNull String name, + @NotNull Image image, + @NotNull Flavor flavor, + @NotNull Map<String, String> labels, + @NotNull Map<String, ?> meta, + boolean start) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + + final ServiceFlavor internalFlavor = + Objects.requireNonNull(service.flavorById.get(flavor.getUid()), "Unknown flavor"); + final ServiceImage internalImage = + Objects.requireNonNull(service.imageById.get(image.getUid()), "Unknown image"); + + ServiceServer server = new ServiceServer(service, uid, name, internalFlavor, internalImage, labels, meta); + + service.serverById.put(uid, server); + service.servers.add(server); + + if (start) { + server.start(); + } + + return server; + } + + @Nullable + @Override + public Server findServer(@NotNull UUID id) { + checkOpen(); + return service.serverById.get(id); + } + + @NotNull + @Override + public List<Server> queryServers() { + checkOpen(); + + return new ArrayList<>(service.servers); + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public String toString() { + return "ComputeService.Client"; + } + } + + /** + * A request to schedule a {@link ServiceServer} onto one of the {@link Host}s. + */ + static class SchedulingRequest { + final ServiceServer server; + final long submitTime; + + boolean isCancelled; + + SchedulingRequest(ServiceServer server, long submitTime) { + this.server = server; + this.submitTime = submitTime; + } + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java new file mode 100644 index 00000000..6e2cdcb4 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import org.opendc.compute.service.driver.Host; + +/** + * A view of a {@link Host} as seen from the {@link ComputeService}. + */ +public class HostView { + private final Host host; + int instanceCount; + long availableMemory; + int provisionedCores; + + /** + * Construct a {@link HostView} instance. + * + * @param host The host to create a view of. + */ + public HostView(Host host) { + this.host = host; + this.availableMemory = host.getModel().memoryCapacity(); + } + + /** + * The {@link Host} this is a view of. + */ + public Host getHost() { + return host; + } + + /** + * Return the number of instances on this host. + */ + public int getInstanceCount() { + return instanceCount; + } + + /** + * Return the available memory of the host. + */ + public long getAvailableMemory() { + return availableMemory; + } + + /** + * Return the provisioned cores on the host. + */ + public int getProvisionedCores() { + return provisionedCores; + } + + @Override + public String toString() { + return "HostView[host=" + host + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java new file mode 100644 index 00000000..dba87e2c --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.opendc.compute.api.Flavor; + +/** + * Implementation of {@link Flavor} provided by {@link ComputeService}. + */ +public final class ServiceFlavor implements Flavor { + private final ComputeService service; + private final UUID uid; + private final String name; + private final int cpuCount; + private final long memorySize; + private final Map<String, String> labels; + private final Map<String, ?> meta; + + ServiceFlavor( + ComputeService service, + UUID uid, + String name, + int cpuCount, + long memorySize, + Map<String, String> labels, + Map<String, ?> meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.cpuCount = cpuCount; + this.memorySize = memorySize; + this.labels = labels; + this.meta = meta; + } + + @Override + public int getCpuCount() { + return cpuCount; + } + + @Override + public long getMemorySize() { + return memorySize; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public Map<String, String> getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map<String, Object> getMeta() { + return Collections.unmodifiableMap(meta); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + service.delete(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceFlavor flavor = (ServiceFlavor) o; + return service.equals(flavor.service) && uid.equals(flavor.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Flavor[uid=" + uid + ",name=" + name + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java new file mode 100644 index 00000000..706be483 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.opendc.compute.api.Image; + +/** + * Implementation of {@link Image} provided by {@link ComputeService}. + */ +public final class ServiceImage implements Image { + private final ComputeService service; + private final UUID uid; + private final String name; + private final Map<String, String> labels; + private final Map<String, ?> meta; + + ServiceImage(ComputeService service, UUID uid, String name, Map<String, String> labels, Map<String, ?> meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.labels = labels; + this.meta = meta; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public Map<String, String> getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map<String, Object> getMeta() { + return Collections.unmodifiableMap(meta); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + service.delete(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceImage image = (ServiceImage) o; + return service.equals(image.service) && uid.equals(image.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Image[uid=" + uid + ",name=" + name + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java new file mode 100644 index 00000000..265feac0 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; +import org.opendc.compute.api.ServerWatcher; +import org.opendc.compute.service.driver.Host; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link Server} provided by {@link ComputeService}. + */ +public final class ServiceServer implements Server { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceServer.class); + + private final ComputeService service; + private final UUID uid; + private final String name; + private final ServiceFlavor flavor; + private final ServiceImage image; + private final Map<String, String> labels; + private final Map<String, ?> meta; + + private final List<ServerWatcher> watchers = new ArrayList<>(); + private ServerState state = ServerState.TERMINATED; + Instant launchedAt = null; + Host host = null; + private ComputeService.SchedulingRequest request = null; + + ServiceServer( + ComputeService service, + UUID uid, + String name, + ServiceFlavor flavor, + ServiceImage image, + Map<String, String> labels, + Map<String, ?> meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.flavor = flavor; + this.image = image; + this.labels = labels; + this.meta = meta; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public ServiceFlavor getFlavor() { + return flavor; + } + + @NotNull + @Override + public ServiceImage getImage() { + return image; + } + + @NotNull + @Override + public Map<String, String> getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map<String, Object> getMeta() { + return Collections.unmodifiableMap(meta); + } + + @NotNull + @Override + public ServerState getState() { + return state; + } + + @Nullable + @Override + public Instant getLaunchedAt() { + return launchedAt; + } + + /** + * Return the {@link Host} on which the server is running or <code>null</code> if it is not running on a host. + */ + public Host getHost() { + return host; + } + + @Override + public void start() { + switch (state) { + case PROVISIONING: + LOGGER.debug("User tried to start server but request is already pending: doing nothing"); + case RUNNING: + LOGGER.debug("User tried to start server but server is already running"); + break; + case DELETED: + LOGGER.warn("User tried to start deleted server"); + throw new IllegalStateException("Server is deleted"); + default: + LOGGER.info("User requested to start server {}", uid); + setState(ServerState.PROVISIONING); + assert request == null : "Scheduling request already active"; + request = service.schedule(this); + break; + } + } + + @Override + public void stop() { + switch (state) { + case PROVISIONING: + cancelProvisioningRequest(); + setState(ServerState.TERMINATED); + break; + case RUNNING: + case ERROR: + final Host host = this.host; + if (host == null) { + throw new IllegalStateException("Server not running"); + } + host.stop(this); + break; + } + } + + @Override + public void watch(@NotNull ServerWatcher watcher) { + watchers.add(watcher); + } + + @Override + public void unwatch(@NotNull ServerWatcher watcher) { + watchers.remove(watcher); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + switch (state) { + case PROVISIONING: + case TERMINATED: + cancelProvisioningRequest(); + service.delete(this); + setState(ServerState.DELETED); + break; + case RUNNING: + case ERROR: + final Host host = this.host; + if (host == null) { + throw new IllegalStateException("Server not running"); + } + host.delete(this); + service.delete(this); + setState(ServerState.DELETED); + break; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceServer server = (ServiceServer) o; + return service.equals(server.service) && uid.equals(server.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Server[uid=" + uid + ",name=" + name + ",state=" + state + "]"; + } + + void setState(ServerState state) { + if (this.state != state) { + for (ServerWatcher watcher : watchers) { + watcher.onStateChanged(this, state); + } + } + + this.state = state; + } + + /** + * Cancel the provisioning request if active. + */ + private void cancelProvisioningRequest() { + final ComputeService.SchedulingRequest request = this.request; + if (request != null) { + this.request = null; + request.isCancelled = true; + } + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java index efcc0f2c..760d7f1a 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,116 +20,118 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver +package org.opendc.compute.service.driver; -import org.opendc.compute.api.Server -import org.opendc.compute.service.driver.telemetry.GuestCpuStats -import org.opendc.compute.service.driver.telemetry.GuestSystemStats -import org.opendc.compute.service.driver.telemetry.HostCpuStats -import org.opendc.compute.service.driver.telemetry.HostSystemStats -import java.util.UUID +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.opendc.compute.api.Server; +import org.opendc.compute.service.driver.telemetry.GuestCpuStats; +import org.opendc.compute.service.driver.telemetry.GuestSystemStats; +import org.opendc.compute.service.driver.telemetry.HostCpuStats; +import org.opendc.compute.service.driver.telemetry.HostSystemStats; /** - * Base interface for representing compute resources that host virtualized [Server] instances. + * Base interface for representing compute resources that host virtualized {@link Server} instances. */ public interface Host { /** - * A unique identifier representing the host. + * Return a unique identifier representing the host. */ - public val uid: UUID + UUID getUid(); /** - * The name of this host. + * Return the name of this host. */ - public val name: String + String getName(); /** - * The machine model of the host. + * Return the machine model of the host. */ - public val model: HostModel + HostModel getModel(); /** - * The state of the host. + * Return the state of the host. */ - public val state: HostState + HostState getState(); /** - * Meta-data associated with the host. + * Return the meta-data associated with the host. */ - public val meta: Map<String, Any> + Map<String, ?> getMeta(); /** - * The [Server] instances known to the host. + * Return the {@link Server} instances known to the host. */ - public val instances: Set<Server> + Set<Server> getInstances(); /** - * Determine whether the specified [instance][server] can still fit on this host. + * Determine whether the specified <code>server</code> can still fit on this host. */ - public fun canFit(server: Server): Boolean + boolean canFit(Server server); /** - * Register the specified [instance][server] on the host. + * Register the specified <code>server</code> on the host. */ - public fun spawn(server: Server) + void spawn(Server server); /** - * Determine whether the specified [instance][server] exists on the host. + * Determine whether the specified <code>server</code> exists on the host. */ - public operator fun contains(server: Server): Boolean + boolean contains(Server server); /** - * Start the server [instance][server] if it is currently not running on this host. + * Start the server if it is currently not running on this host. * * @throws IllegalArgumentException if the server is not present on the host. */ - public fun start(server: Server) + void start(Server server); /** - * Stop the server [instance][server] if it is currently running on this host. + * Stop the server if it is currently running on this host. * * @throws IllegalArgumentException if the server is not present on the host. */ - public fun stop(server: Server) + void stop(Server server); /** - * Delete the specified [instance][server] on this host and cleanup all resources associated with it. + * Delete the specified <code>server</code> on this host and cleanup all resources associated with it. */ - public fun delete(server: Server) + void delete(Server server); /** * Add a [HostListener] to this host. */ - public fun addListener(listener: HostListener) + void addListener(HostListener listener); /** * Remove a [HostListener] from this host. */ - public fun removeListener(listener: HostListener) + void removeListener(HostListener listener); /** * Query the system statistics of the host. */ - public fun getSystemStats(): HostSystemStats + HostSystemStats getSystemStats(); /** - * Query the system statistics of a [Server] that is located on this host. + * Query the system statistics of a {@link Server} that is located on this host. * - * @param server The [Server] to obtain the system statistics of. + * @param server The {@link Server} to obtain the system statistics of. * @throws IllegalArgumentException if the server is not present on the host. */ - public fun getSystemStats(server: Server): GuestSystemStats + GuestSystemStats getSystemStats(Server server); /** * Query the CPU statistics of the host. */ - public fun getCpuStats(): HostCpuStats + HostCpuStats getCpuStats(); /** - * Query the CPU statistics of a [Server] that is located on this host. + * Query the CPU statistics of a {@link Server} that is located on this host. * - * @param server The [Server] to obtain the CPU statistics of. + * @param server The {@link Server} to obtain the CPU statistics of. * @throws IllegalArgumentException if the server is not present on the host. */ - public fun getCpuStats(server: Server): GuestCpuStats + GuestCpuStats getCpuStats(Server server); } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java index f076cae3..feefca40 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +20,22 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver +package org.opendc.compute.service.driver; -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; /** - * Listener interface for events originating from a [Host]. + * Listener interface for events originating from a {@link Host}. */ public interface HostListener { /** - * This method is invoked when the state of an [instance][server] on [host] changes. + * This method is invoked when the state of <code>server</code> on <code>host</code> changes. */ - public fun onStateChanged(host: Host, server: Server, newState: ServerState) {} + default void onStateChanged(Host host, Server server, ServerState newState) {} /** - * This method is invoked when the state of a [Host] has changed. + * This method is invoked when the state of a {@link Host} has changed. */ - public fun onStateChanged(host: Host, newState: HostState) {} + default void onStateChanged(Host host, HostState newState) {} } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java index f3b94e3d..9caa6da7 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +20,13 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver +package org.opendc.compute.service.driver; /** - * Describes the static machine properties of the host. + * Record describing the static machine properties of the host. * - * @property cpuCapacity The total CPU capacity of the host in MHz. - * @property cpuCount The number of logical processing cores available for this host. - * @property memoryCapacity The amount of memory available for this host in MB. + * @param cpuCapacity The total CPU capacity of the host in MHz. + * @param cpuCount The number of logical processing cores available for this host. + * @param memoryCapacity The amount of memory available for this host in MB. */ -public data class HostModel( - public val cpuCapacity: Double, - public val cpuCount: Int, - public val memoryCapacity: Long -) +public record HostModel(double cpuCapacity, int cpuCount, long memoryCapacity) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java index 544b6530..ce12a67e 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,12 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver +package org.opendc.compute.service.driver; /** * The state of a host. */ -public enum class HostState { +public enum HostState { /** * The host is up and able to host guests. */ diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java index b5d63471..0b78c7ea 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java @@ -20,25 +20,24 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver.telemetry +package org.opendc.compute.service.driver.telemetry; /** * Statistics about the CPUs of a guest. * - * @property activeTime The cumulative time (in seconds) that the CPUs of the guest were actively running. - * @property idleTime The cumulative time (in seconds) the CPUs of the guest were idle. - * @property stealTime The cumulative CPU time (in seconds) that the guest was ready to run, but not granted time by the host. - * @property lostTime The cumulative CPU time (in seconds) that was lost due to interference with other machines. - * @property capacity The available CPU capacity of the guest (in MHz). - * @property usage Amount of CPU resources (in MHz) actually used by the guest. - * @property utilization Utilization of the CPU resources (in %) relative to the total CPU capacity. + * @param activeTime The cumulative time (in seconds) that the CPUs of the guest were actively running. + * @param idleTime The cumulative time (in seconds) the CPUs of the guest were idle. + * @param stealTime The cumulative CPU time (in seconds) that the guest was ready to run, but not granted time by the host. + * @param lostTime The cumulative CPU time (in seconds) that was lost due to interference with other machines. + * @param capacity The available CPU capacity of the guest (in MHz). + * @param usage Amount of CPU resources (in MHz) actually used by the guest. + * @param utilization The utilization of the CPU resources (in %) relative to the total CPU capacity. */ -public data class GuestCpuStats( - val activeTime: Long, - val idleTime: Long, - val stealTime: Long, - val lostTime: Long, - val capacity: Double, - val usage: Double, - val utilization: Double -) +public record GuestCpuStats( + long activeTime, + long idleTime, + long stealTime, + long lostTime, + double capacity, + double usage, + double utilization) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java index 6fec5175..dbf98dd5 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java @@ -20,20 +20,16 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver.telemetry +package org.opendc.compute.service.driver.telemetry; -import java.time.Duration -import java.time.Instant +import java.time.Duration; +import java.time.Instant; /** * System-level statistics of a guest. * - * @property uptime The cumulative uptime of the guest since last boot (in ms). - * @property downtime The cumulative downtime of the guest since last boot (in ms). - * @property bootTime The time at which the guest booted. + * @param uptime The cumulative uptime of the guest since last boot (in ms). + * @param downtime The cumulative downtime of the guest since last boot (in ms). + * @param bootTime The time at which the guest booted. */ -public data class GuestSystemStats( - val uptime: Duration, - val downtime: Duration, - val bootTime: Instant? -) +public record GuestSystemStats(Duration uptime, Duration downtime, Instant bootTime) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java index 55e23c0e..d1c2328b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java @@ -20,28 +20,27 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver.telemetry +package org.opendc.compute.service.driver.telemetry; /** * Statistics about the CPUs of a host. * - * @property activeTime The cumulative time (in seconds) that the CPUs of the host were actively running. - * @property idleTime The cumulative time (in seconds) the CPUs of the host were idle. - * @property stealTime The cumulative CPU time (in seconds) that virtual machines were ready to run, but were not able to. - * @property lostTime The cumulative CPU time (in seconds) that was lost due to interference between virtual machines. - * @property capacity The available CPU capacity of the host (in MHz). - * @property demand Amount of CPU resources (in MHz) the guests would use if there were no CPU contention or CPU - * limits. - * @property usage Amount of CPU resources (in MHz) actually used by the host. - * @property utilization Utilization of the CPU resources (in %) relative to the total CPU capacity. + * @param activeTime The cumulative time (in seconds) that the CPUs of the host were actively running. + * @param idleTime The cumulative time (in seconds) the CPUs of the host were idle. + * @param stealTime The cumulative CPU time (in seconds) that virtual machines were ready to run, but were not able to. + * @param lostTime The cumulative CPU time (in seconds) that was lost due to interference between virtual machines. + * @param capacity The available CPU capacity of the host (in MHz). + * @param demand Amount of CPU resources (in MHz) the guests would use if there were no CPU contention or CPU + * limits. + * @param usage Amount of CPU resources (in MHz) actually used by the host. + * @param utilization The utilization of the CPU resources (in %) relative to the total CPU capacity. */ -public data class HostCpuStats( - val activeTime: Long, - val idleTime: Long, - val stealTime: Long, - val lostTime: Long, - val capacity: Double, - val demand: Double, - val usage: Double, - val utilization: Double -) +public record HostCpuStats( + long activeTime, + long idleTime, + long stealTime, + long lostTime, + double capacity, + double demand, + double usage, + double utilization) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java index 56bd017d..c0928f1b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java @@ -20,32 +20,31 @@ * SOFTWARE. */ -package org.opendc.compute.service.driver.telemetry +package org.opendc.compute.service.driver.telemetry; -import java.time.Duration -import java.time.Instant +import java.time.Duration; +import java.time.Instant; /** * System-level statistics of a host. - - * @property uptime The cumulative uptime of the host since last boot (in ms). - * @property downtime The cumulative downtime of the host since last boot (in ms). - * @property bootTime The time at which the server started. - * @property powerUsage Instantaneous power usage of the system (in W). - * @property energyUsage The cumulative energy usage of the system (in J). - * @property guestsTerminated The number of guests that are in a terminated state. - * @property guestsRunning The number of guests that are in a running state. - * @property guestsError The number of guests that are in an error state. - * @property guestsInvalid The number of guests that are in an unknown state. + * + * @param uptime The cumulative uptime of the host since last boot (in ms). + * @param downtime The cumulative downtime of the host since last boot (in ms). + * @param bootTime The time at which the server started. + * @param powerUsage Instantaneous power usage of the system (in W). + * @param energyUsage The cumulative energy usage of the system (in J). + * @param guestsTerminated The number of guests that are in a terminated state. + * @param guestsRunning The number of guests that are in a running state. + * @param guestsError The number of guests that are in an error state. + * @param guestsInvalid The number of guests that are in an unknown state. */ -public data class HostSystemStats( - val uptime: Duration, - val downtime: Duration, - val bootTime: Instant?, - val powerUsage: Double, - val energyUsage: Double, - val guestsTerminated: Int, - val guestsRunning: Int, - val guestsError: Int, - val guestsInvalid: Int -) +public record HostSystemStats( + Duration uptime, + Duration downtime, + Instant bootTime, + double powerUsage, + double energyUsage, + int guestsTerminated, + int guestsRunning, + int guestsError, + int guestsInvalid) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java index 6e9f458a..2157169b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java @@ -20,29 +20,26 @@ * SOFTWARE. */ -package org.opendc.compute.service.telemetry - -import org.opendc.compute.service.ComputeService +package org.opendc.compute.service.telemetry; /** * Statistics about the scheduling component of the [ComputeService]. * - * @property hostsAvailable The number of hosts currently available for scheduling. - * @property hostsUnavailable The number of hosts unavailable for scheduling. - * @property attemptsSuccess Scheduling attempts that resulted into an allocation onto a host. - * @property attemptsFailure The number of failed scheduling attempt due to insufficient capacity at the moment. - * @property attemptsError The number of scheduling attempts that failed due to system error. - * @property serversTotal The number of servers registered with the service. - * @property serversPending The number of servers that are pending to be scheduled. - * @property serversActive The number of servers that are currently managed by the service and running. + * @param hostsAvailable The number of hosts currently available for scheduling. + * @param hostsUnavailable The number of hosts unavailable for scheduling. + * @param attemptsSuccess Scheduling attempts that resulted into an allocation onto a host. + * @param attemptsFailure The number of failed scheduling attempt due to insufficient capacity at the moment. + * @param attemptsError The number of scheduling attempts that failed due to system error. + * @param serversTotal The number of servers registered with the service. + * @param serversPending The number of servers that are pending to be scheduled. + * @param serversActive The number of servers that are currently managed by the service and running. */ -public data class SchedulerStats( - val hostsAvailable: Int, - val hostsUnavailable: Int, - val attemptsSuccess: Long, - val attemptsFailure: Long, - val attemptsError: Long, - val serversTotal: Int, - val serversPending: Int, - val serversActive: Int -) +public record SchedulerStats( + int hostsAvailable, + int hostsUnavailable, + long attemptsSuccess, + long attemptsFailure, + long attemptsError, + int serversTotal, + int serversPending, + int serversActive) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt deleted file mode 100644 index 9d7dcba6..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service - -import org.opendc.common.Dispatcher -import org.opendc.compute.api.ComputeClient -import org.opendc.compute.api.Server -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.service.telemetry.SchedulerStats -import java.time.Duration - -/** - * The [ComputeService] hosts the API implementation of the OpenDC Compute service. - */ -public interface ComputeService : AutoCloseable { - /** - * The servers that are registered with the "compute" service. - */ - public val servers: List<Server> - - /** - * The hosts that are registered with the "compute" service. - */ - public val hosts: Set<Host> - - /** - * Create a new [ComputeClient] to control the compute service. - */ - public fun newClient(): ComputeClient - - /** - * Add a [host] to the scheduling pool of the compute service. - */ - public fun addHost(host: Host) - - /** - * Remove a [host] from the scheduling pool of the compute service. - */ - public fun removeHost(host: Host) - - /** - * Terminate the lifecycle of the compute service, stopping all running instances. - */ - public override fun close() - - /** - * Lookup the [Host] that currently hosts the specified [server]. - */ - public fun lookupHost(server: Server): Host? - - /** - * Collect the statistics about the scheduler component of this service. - */ - public fun getSchedulerStats(): SchedulerStats - - public companion object { - /** - * Construct a new [ComputeService] implementation. - * - * @param dispatcher The [Dispatcher] for scheduling future events. - * @param scheduler The scheduler implementation to use. - * @param schedulingQuantum The interval between scheduling cycles. - */ - public operator fun invoke( - dispatcher: Dispatcher, - scheduler: ComputeScheduler, - schedulingQuantum: Duration = Duration.ofMinutes(5) - ): ComputeService { - return ComputeServiceImpl(dispatcher, scheduler, schedulingQuantum) - } - } -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt deleted file mode 100644 index 4a8d3046..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import java.util.UUID - -/** - * A [Flavor] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientFlavor(private val delegate: Flavor) : Flavor { - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var cpuCount: Int = delegate.cpuCount - private set - - override var memorySize: Long = delegate.memorySize - private set - - override var labels: Map<String, String> = delegate.labels.toMap() - private set - - override var meta: Map<String, Any> = delegate.meta.toMap() - private set - - override suspend fun delete() { - delegate.delete() - } - - override suspend fun refresh() { - delegate.refresh() - - name = delegate.name - cpuCount = delegate.cpuCount - memorySize = delegate.memorySize - labels = delegate.labels - meta = delegate.meta - } - - override fun equals(other: Any?): Boolean = other is Flavor && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Flavor[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt deleted file mode 100644 index f0032acf..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Image -import java.util.UUID - -/** - * An [Image] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientImage(private val delegate: Image) : Image { - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var labels: Map<String, String> = delegate.labels.toMap() - private set - - override var meta: Map<String, Any> = delegate.meta.toMap() - private set - - override suspend fun delete() { - delegate.delete() - refresh() - } - - override suspend fun refresh() { - delegate.refresh() - - name = delegate.name - labels = delegate.labels - meta = delegate.meta - } - - override fun equals(other: Any?): Boolean = other is Image && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Image[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt deleted file mode 100644 index 6cd7d30f..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import org.opendc.compute.api.Image -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.api.ServerWatcher -import java.time.Instant -import java.util.UUID - -/** - * A [Server] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientServer(private val delegate: Server) : Server, ServerWatcher { - private val watchers = mutableListOf<ServerWatcher>() - - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var flavor: Flavor = delegate.flavor - private set - - override var image: Image = delegate.image - private set - - override var labels: Map<String, String> = delegate.labels.toMap() - private set - - override var meta: Map<String, Any> = delegate.meta.toMap() - private set - - override var state: ServerState = delegate.state - private set - - override var launchedAt: Instant? = null - private set - - override suspend fun start() { - delegate.start() - refresh() - } - - override suspend fun stop() { - delegate.stop() - refresh() - } - - override suspend fun delete() { - delegate.delete() - refresh() - } - - override fun watch(watcher: ServerWatcher) { - if (watchers.isEmpty()) { - delegate.watch(this) - } - - watchers += watcher - } - - override fun unwatch(watcher: ServerWatcher) { - watchers += watcher - - if (watchers.isEmpty()) { - delegate.unwatch(this) - } - } - - override suspend fun refresh() { - delegate.refresh() - - name = delegate.name - flavor = delegate.flavor - image = delegate.image - labels = delegate.labels - meta = delegate.meta - state = delegate.state - launchedAt = delegate.launchedAt - } - - override fun onStateChanged(server: Server, newState: ServerState) { - val watchers = watchers - - for (watcher in watchers) { - watcher.onStateChanged(this, newState) - } - } - - override fun equals(other: Any?): Boolean = other is Server && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Server[uid=$uid,name=$name,state=$state]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt deleted file mode 100644 index 77932545..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import mu.KotlinLogging -import org.opendc.common.Dispatcher -import org.opendc.common.util.Pacer -import org.opendc.compute.api.ComputeClient -import org.opendc.compute.api.Flavor -import org.opendc.compute.api.Image -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostListener -import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.service.telemetry.SchedulerStats -import java.time.Duration -import java.time.Instant -import java.util.ArrayDeque -import java.util.Deque -import java.util.Random -import java.util.UUID -import kotlin.math.max - -/** - * Internal implementation of the OpenDC Compute service. - * - * @param dispatcher The [Dispatcher] for scheduling future events. - * @param scheduler The scheduler implementation to use. - * @param schedulingQuantum The interval between scheduling cycles. - */ -internal class ComputeServiceImpl( - private val dispatcher: Dispatcher, - private val scheduler: ComputeScheduler, - schedulingQuantum: Duration -) : ComputeService, HostListener { - /** - * The logger instance of this server. - */ - private val logger = KotlinLogging.logger {} - - /** - * The [Random] instance used to generate unique identifiers for the objects. - */ - private val random = Random(0) - - /** - * A mapping from host to host view. - */ - private val hostToView = mutableMapOf<Host, HostView>() - - /** - * The available hypervisors. - */ - private val availableHosts: MutableSet<HostView> = mutableSetOf() - - /** - * The servers that should be launched by the service. - */ - private val queue: Deque<SchedulingRequest> = ArrayDeque() - - /** - * The active servers in the system. - */ - private val activeServers: MutableMap<Server, Host> = mutableMapOf() - - /** - * The registered flavors for this compute service. - */ - internal val flavorById = mutableMapOf<UUID, InternalFlavor>() - - /** - * The registered images for this compute service. - */ - internal val imageById = mutableMapOf<UUID, InternalImage>() - - /** - * The registered servers for this compute service. - */ - private val serverById = mutableMapOf<UUID, InternalServer>() - override val servers: MutableList<Server> = mutableListOf() - - override val hosts: Set<Host> - get() = hostToView.keys - - private val clock = dispatcher.timeSource - private var maxCores = 0 - private var maxMemory = 0L - private var _attemptsSuccess = 0L - private var _attemptsFailure = 0L - private var _attemptsError = 0L - private var _serversPending = 0 - private var _serversActive = 0 - private var isClosed = false - - /** - * The [Pacer] to use for scheduling the scheduler cycles. - */ - private val pacer = Pacer(dispatcher, schedulingQuantum.toMillis()) { doSchedule() } - - override fun newClient(): ComputeClient { - check(!isClosed) { "Service is already closed" } - return object : ComputeClient { - private var isClosed: Boolean = false - - override suspend fun queryFlavors(): List<Flavor> { - check(!isClosed) { "Client is already closed" } - - return flavorById.values.map { ClientFlavor(it) } - } - - override suspend fun findFlavor(id: UUID): Flavor? { - check(!isClosed) { "Client is already closed" } - - return flavorById[id]?.let { ClientFlavor(it) } - } - - override suspend fun newFlavor( - name: String, - cpuCount: Int, - memorySize: Long, - labels: Map<String, String>, - meta: Map<String, Any> - ): Flavor { - check(!isClosed) { "Client is already closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val flavor = InternalFlavor( - this@ComputeServiceImpl, - uid, - name, - cpuCount, - memorySize, - labels, - meta - ) - - flavorById[uid] = flavor - - return ClientFlavor(flavor) - } - - override suspend fun queryImages(): List<Image> { - check(!isClosed) { "Client is already closed" } - - return imageById.values.map { ClientImage(it) } - } - - override suspend fun findImage(id: UUID): Image? { - check(!isClosed) { "Client is already closed" } - - return imageById[id]?.let { ClientImage(it) } - } - - override suspend fun newImage(name: String, labels: Map<String, String>, meta: Map<String, Any>): Image { - check(!isClosed) { "Client is already closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val image = InternalImage(this@ComputeServiceImpl, uid, name, labels, meta) - - imageById[uid] = image - - return ClientImage(image) - } - - override suspend fun newServer( - name: String, - image: Image, - flavor: Flavor, - labels: Map<String, String>, - meta: Map<String, Any>, - start: Boolean - ): Server { - check(!isClosed) { "Client is closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val server = InternalServer( - this@ComputeServiceImpl, - uid, - name, - requireNotNull(flavorById[flavor.uid]) { "Unknown flavor" }, - requireNotNull(imageById[image.uid]) { "Unknown image" }, - labels.toMutableMap(), - meta.toMutableMap() - ) - - serverById[uid] = server - servers.add(server) - - if (start) { - server.start() - } - - return ClientServer(server) - } - - override suspend fun findServer(id: UUID): Server? { - check(!isClosed) { "Client is already closed" } - - return serverById[id]?.let { ClientServer(it) } - } - - override suspend fun queryServers(): List<Server> { - check(!isClosed) { "Client is already closed" } - - return serverById.values.map { ClientServer(it) } - } - - override fun close() { - isClosed = true - } - - override fun toString(): String = "ComputeClient" - } - } - - override fun addHost(host: Host) { - // Check if host is already known - if (host in hostToView) { - return - } - - val hv = HostView(host) - maxCores = max(maxCores, host.model.cpuCount) - maxMemory = max(maxMemory, host.model.memoryCapacity) - hostToView[host] = hv - - if (host.state == HostState.UP) { - availableHosts += hv - } - - scheduler.addHost(hv) - host.addListener(this) - } - - override fun removeHost(host: Host) { - val view = hostToView.remove(host) - if (view != null) { - availableHosts.remove(view) - scheduler.removeHost(view) - host.removeListener(this) - } - } - - override fun lookupHost(server: Server): Host? { - if (server is InternalServer) { - return server.host - } - - val internal = requireNotNull(serverById[server.uid]) { "Invalid server passed to lookupHost" } - return internal.host - } - - override fun close() { - if (isClosed) { - return - } - - isClosed = true - pacer.cancel() - } - - override fun getSchedulerStats(): SchedulerStats { - return SchedulerStats( - availableHosts.size, - hostToView.size - availableHosts.size, - _attemptsSuccess, - _attemptsFailure, - _attemptsError, - servers.size, - _serversPending, - _serversActive - ) - } - - internal fun schedule(server: InternalServer): SchedulingRequest { - logger.debug { "Enqueueing server ${server.uid} to be assigned to host." } - val now = clock.millis() - val request = SchedulingRequest(server, now) - - server.launchedAt = Instant.ofEpochMilli(now) - queue.add(request) - _serversPending++ - requestSchedulingCycle() - return request - } - - internal fun delete(flavor: InternalFlavor) { - flavorById.remove(flavor.uid) - } - - internal fun delete(image: InternalImage) { - imageById.remove(image.uid) - } - - internal fun delete(server: InternalServer) { - serverById.remove(server.uid) - servers.remove(server) - } - - /** - * Indicate that a new scheduling cycle is needed due to a change to the service's state. - */ - private fun requestSchedulingCycle() { - // Bail out in case the queue is empty. - if (queue.isEmpty()) { - return - } - - pacer.enqueue() - } - - /** - * Run a single scheduling iteration. - */ - private fun doSchedule() { - while (queue.isNotEmpty()) { - val request = queue.peek() - - if (request.isCancelled) { - queue.poll() - _serversPending-- - continue - } - - val server = request.server - val hv = scheduler.select(request.server) - if (hv == null || !hv.host.canFit(server)) { - logger.trace { "Server $server selected for scheduling but no capacity available for it at the moment" } - - if (server.flavor.memorySize > maxMemory || server.flavor.cpuCount > maxCores) { - // Remove the incoming image - queue.poll() - _serversPending-- - _attemptsFailure++ - - logger.warn { "Failed to spawn $server: does not fit [${clock.instant()}]" } - - server.state = ServerState.TERMINATED - continue - } else { - break - } - } - - val host = hv.host - - // Remove request from queue - queue.poll() - _serversPending-- - - logger.info { "Assigned server $server to host $host." } - - try { - server.host = host - - host.spawn(server) - host.start(server) - - _serversActive++ - _attemptsSuccess++ - - hv.instanceCount++ - hv.provisionedCores += server.flavor.cpuCount - hv.availableMemory -= server.flavor.memorySize - - activeServers[server] = host - } catch (e: Throwable) { - logger.error(e) { "Failed to deploy VM" } - _attemptsError++ - } - } - } - - /** - * A request to schedule an [InternalServer] onto one of the [Host]s. - */ - internal data class SchedulingRequest(val server: InternalServer, val submitTime: Long) { - /** - * A flag to indicate that the request is cancelled. - */ - var isCancelled: Boolean = false - } - - override fun onStateChanged(host: Host, newState: HostState) { - when (newState) { - HostState.UP -> { - logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } - - val hv = hostToView[host] - if (hv != null) { - // Corner case for when the hypervisor already exists - availableHosts += hv - } - - // Re-schedule on the new machine - requestSchedulingCycle() - } - else -> { - logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } - - val hv = hostToView[host] ?: return - availableHosts -= hv - - requestSchedulingCycle() - } - } - } - - override fun onStateChanged(host: Host, server: Server, newState: ServerState) { - require(server is InternalServer) { "Invalid server type passed to service" } - - if (server.host != host) { - // This can happen when a server is rescheduled and started on another machine, while being deleted from - // the old machine. - return - } - - server.state = newState - - if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { - logger.info { "[${clock.instant()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } - - if (activeServers.remove(server) != null) { - _serversActive-- - } - - val hv = hostToView[host] - if (hv != null) { - hv.provisionedCores -= server.flavor.cpuCount - hv.instanceCount-- - hv.availableMemory += server.flavor.memorySize - } else { - logger.error { "Unknown host $host" } - } - - // Try to reschedule if needed - requestSchedulingCycle() - } - } -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt deleted file mode 100644 index 0876209a..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.driver.Host -import java.util.UUID - -/** - * A view of a [Host] as seen from the [ComputeService] - */ -public class HostView(public val host: Host) { - /** - * The unique identifier of the host. - */ - public val uid: UUID - get() = host.uid - - public var instanceCount: Int = 0 - public var availableMemory: Long = host.model.memoryCapacity - public var provisionedCores: Int = 0 - - override fun toString(): String = "HostView[host=$host]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt deleted file mode 100644 index acd87dfc..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import java.util.UUID - -/** - * Internal stateful representation of a [Flavor]. - */ -internal class InternalFlavor( - private val service: ComputeServiceImpl, - override val uid: UUID, - name: String, - cpuCount: Int, - memorySize: Long, - labels: Map<String, String>, - meta: Map<String, Any> -) : Flavor { - override var name: String = name - private set - - override var cpuCount: Int = cpuCount - private set - - override var memorySize: Long = memorySize - private set - - override val labels: MutableMap<String, String> = labels.toMutableMap() - - override val meta: MutableMap<String, Any> = meta.toMutableMap() - - override suspend fun refresh() { - // No-op: this object is the source-of-truth - } - - override suspend fun delete() { - service.delete(this) - } - - override fun equals(other: Any?): Boolean = other is Flavor && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Flavor[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt deleted file mode 100644 index a0a35a55..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Image -import java.util.UUID - -/** - * Internal stateful representation of an [Image]. - */ -internal class InternalImage( - private val service: ComputeServiceImpl, - override val uid: UUID, - override val name: String, - labels: Map<String, String>, - meta: Map<String, Any> -) : Image { - - override val labels: MutableMap<String, String> = labels.toMutableMap() - - override val meta: MutableMap<String, Any> = meta.toMutableMap() - - override suspend fun refresh() { - // No-op: this object is the source-of-truth - } - - override suspend fun delete() { - service.delete(this) - } - - override fun equals(other: Any?): Boolean = other is Image && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Image[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt deleted file mode 100644 index e3bae405..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import mu.KotlinLogging -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.api.ServerWatcher -import org.opendc.compute.service.driver.Host -import java.time.Instant -import java.util.UUID - -/** - * Internal implementation of the [Server] interface. - */ -internal class InternalServer( - private val service: ComputeServiceImpl, - override val uid: UUID, - override val name: String, - override val flavor: InternalFlavor, - override val image: InternalImage, - override val labels: MutableMap<String, String>, - override val meta: MutableMap<String, Any> -) : Server { - /** - * The logger instance of this server. - */ - private val logger = KotlinLogging.logger {} - - /** - * The watchers of this server object. - */ - private val watchers = mutableListOf<ServerWatcher>() - - /** - * The [Host] that has been assigned to host the server. - */ - @JvmField internal var host: Host? = null - - /** - * The most recent timestamp when the server entered a provisioning state. - */ - override var launchedAt: Instant? = null - - /** - * The current scheduling request. - */ - private var request: ComputeServiceImpl.SchedulingRequest? = null - - override suspend fun start() { - when (state) { - ServerState.RUNNING -> { - logger.debug { "User tried to start server but server is already running" } - return - } - ServerState.PROVISIONING -> { - logger.debug { "User tried to start server but request is already pending: doing nothing" } - return - } - ServerState.DELETED -> { - logger.warn { "User tried to start terminated server" } - throw IllegalStateException("Server is terminated") - } - else -> { - logger.info { "User requested to start server $uid" } - state = ServerState.PROVISIONING - assert(request == null) { "Scheduling request already active" } - request = service.schedule(this) - } - } - } - - override suspend fun stop() { - when (state) { - ServerState.PROVISIONING -> { - cancelProvisioningRequest() - state = ServerState.TERMINATED - } - ServerState.RUNNING, ServerState.ERROR -> { - val host = checkNotNull(host) { "Server not running" } - host.stop(this) - } - ServerState.TERMINATED, ServerState.DELETED -> {} // No work needed - } - } - - override suspend fun delete() { - when (state) { - ServerState.PROVISIONING, ServerState.TERMINATED -> { - cancelProvisioningRequest() - service.delete(this) - state = ServerState.DELETED - } - ServerState.RUNNING, ServerState.ERROR -> { - val host = checkNotNull(host) { "Server not running" } - host.delete(this) - service.delete(this) - state = ServerState.DELETED - } - else -> {} // No work needed - } - } - - override fun watch(watcher: ServerWatcher) { - watchers += watcher - } - - override fun unwatch(watcher: ServerWatcher) { - watchers -= watcher - } - - override suspend fun refresh() { - // No-op: this object is the source-of-truth - } - - override var state: ServerState = ServerState.TERMINATED - set(value) { - if (value != field) { - watchers.forEach { it.onStateChanged(this, value) } - } - - field = value - } - - /** - * Cancel the provisioning request if active. - */ - private fun cancelProvisioningRequest() { - val request = request - if (request != null) { - this.request = null - request.isCancelled = true - } - } - - override fun equals(other: Any?): Boolean = other is Server && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Server[uid=$uid,state=$state]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt index a2ab3a2e..0ccaf991 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt @@ -24,7 +24,7 @@ package org.opendc.compute.service.scheduler import org.opendc.compute.api.Server import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A generic scheduler interface used by the [ComputeService] to select hosts to place [Server]s on. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt index 0840ba7e..18a319e9 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.filters.HostFilter import org.opendc.compute.service.scheduler.weights.HostWeigher import java.util.SplittableRandom diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt index 284c1f91..4339b3de 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt @@ -24,7 +24,7 @@ package org.opendc.compute.service.scheduler import mu.KotlinLogging import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * Policy replaying VM-cluster assignment. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt index fb842415..b562f838 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt @@ -23,8 +23,8 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server +import org.opendc.compute.service.HostView import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.internal.HostView /** * A [HostFilter] that filters on active hosts. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt index f6736ac0..4a9f41c5 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import java.util.UUID /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt index 9e909ca6..78010fee 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.FilterScheduler /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt index ed6674b1..5aa38a88 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the number of instances on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt index 8a7a646c..275e8f1c 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the memory requirements of a [Server] and the RAM available on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt index 090e1437..c3753866 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import java.util.UUID /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt index 791710c8..d4dff76b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the vCPU speed requirements of a [Server] and the available diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt index abdd79f1..448a6189 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the vCPU requirements of a [Server] and the available vCPUs on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt index d668fdaf..f79d6d88 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the available memory per core on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt index aa8a9d53..01799122 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.FilterScheduler /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt index 732cbe03..bfb583a2 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the number of instances on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt index d18d31f4..bb837fbe 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the available RAM (memory) on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt index a86226e2..f15f60c9 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the difference required vCPU capacity and the available CPU capacity. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt index 4a22269b..169ad8cb 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the remaining number of vCPUs available. diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index b5685aba..4dc1cfa8 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -50,6 +50,7 @@ import org.opendc.compute.service.scheduler.filters.VCpuFilter import org.opendc.compute.service.scheduler.weights.RamWeigher import org.opendc.simulator.kotlin.SimulationCoroutineScope import org.opendc.simulator.kotlin.runSimulation +import java.time.Duration import java.util.UUID /** @@ -66,7 +67,7 @@ internal class ComputeServiceTest { filters = listOf(ComputeFilter(), VCpuFilter(allocationRatio = 1.0), RamFilter(allocationRatio = 1.0)), weighers = listOf(RamWeigher()) ) - service = ComputeService(scope.dispatcher, computeScheduler) + service = ComputeService(scope.dispatcher, computeScheduler, Duration.ofMinutes(5)) } @Test @@ -170,7 +171,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -183,7 +184,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -196,7 +197,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -210,7 +211,7 @@ internal class ComputeServiceTest { server.start() server.stop() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -231,7 +232,7 @@ internal class ComputeServiceTest { server.start() delay(10L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify { host.canFit(server) } @@ -262,7 +263,7 @@ internal class ComputeServiceTest { listeners.forEach { it.onStateChanged(host, HostState.UP) } delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify { host.canFit(server) } @@ -293,36 +294,13 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify(exactly = 0) { host.canFit(server) } } @Test - fun testServerInvalidType() = scope.runSimulation { - val host = mockk<Host>(relaxUnitFun = true) - val listeners = mutableListOf<HostListener>() - - every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4 * 2600.0, 4, 2048) - every { host.state } returns HostState.UP - every { host.canFit(any()) } returns true - every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } - - service.addHost(host) - - val client = service.newClient() - val flavor = client.newFlavor("test", 1, 1024) - val image = client.newImage("test") - val server = client.newServer("test", image, flavor, start = false) - - assertThrows<IllegalArgumentException> { - listeners.forEach { it.onStateChanged(host, server, ServerState.RUNNING) } - } - } - - @Test fun testServerDeploy() = scope.runSimulation { val host = mockk<Host>(relaxUnitFun = true) val listeners = mutableListOf<HostListener>() @@ -351,7 +329,7 @@ internal class ComputeServiceTest { listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.RUNNING) } - server.refresh() + server.reload() assertEquals(ServerState.RUNNING, server.state) verify { watcher.onStateChanged(server, ServerState.RUNNING) } @@ -359,7 +337,7 @@ internal class ComputeServiceTest { // Stop server listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.TERMINATED) } - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) verify { watcher.onStateChanged(server, ServerState.TERMINATED) } @@ -387,7 +365,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) } } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt index fe92f7f2..7938f789 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt @@ -28,41 +28,27 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test import org.opendc.compute.api.Flavor -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor import java.util.UUID /** - * Test suite for the [InternalFlavor] implementation. + * Test suite for the [ServiceFlavor] implementation. */ -class InternalFlavorTest { +class ServiceFlavorTest { @Test fun testEquality() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - val b = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() - val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - val b = mockk<Flavor>(relaxUnitFun = true) - every { b.uid } returns uid + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf<String, Any>()) + val b = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf<String, Any>()) assertEquals(a, b) } @Test fun testInequalityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf<String, Any>()) val b = mockk<Flavor>(relaxUnitFun = true) every { b.uid } returns UUID.randomUUID() @@ -72,9 +58,9 @@ class InternalFlavorTest { @Test fun testInequalityWithIncorrectType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf<String, Any>()) assertNotEquals(a, Unit) } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt index d60aa628..c36d75f4 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt @@ -28,42 +28,27 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test import org.opendc.compute.api.Image -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor -import org.opendc.compute.service.internal.InternalImage import java.util.UUID /** - * Test suite for the [InternalFlavor] implementation. + * Test suite for the [ServiceFlavor] implementation. */ -class InternalImageTest { +class ServiceImageTest { @Test fun testEquality() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - val b = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() - val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - val b = mockk<Image>(relaxUnitFun = true) - every { b.uid } returns uid + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf<String, Any>()) + val b = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf<String, Any>()) assertEquals(a, b) } @Test fun testInequalityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf<String, Any>()) val b = mockk<Image>(relaxUnitFun = true) every { b.uid } returns UUID.randomUUID() @@ -73,9 +58,9 @@ class InternalImageTest { @Test fun testInequalityWithIncorrectType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf<String, Any>()) assertNotEquals(a, Unit) } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt index 05a8160e..f9fcd27b 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt @@ -22,7 +22,6 @@ package org.opendc.compute.service -import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -35,51 +34,33 @@ import org.junit.jupiter.api.assertThrows import org.opendc.compute.api.Server import org.opendc.compute.api.ServerState import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor -import org.opendc.compute.service.internal.InternalImage -import org.opendc.compute.service.internal.InternalServer import org.opendc.simulator.kotlin.runSimulation import java.util.UUID /** - * Test suite for the [InternalServer] implementation. + * Test suite for the [ServiceServer] implementation. */ -class InternalServerTest { +class ServiceServerTest { @Test fun testEquality() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val b = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - val b = mockk<Server>(relaxUnitFun = true) - every { b.uid } returns uid + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) + val b = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) assertEquals(a, b) } @Test fun testInequalityWithDifferentType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) val b = mockk<Server>(relaxUnitFun = true) every { b.uid } returns UUID.randomUUID() @@ -89,24 +70,24 @@ class InternalServerTest { @Test fun testInequalityWithIncorrectType() { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) assertNotEquals(a, Unit) } @Test fun testStartTerminatedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - every { service.schedule(any()) } answers { ComputeServiceImpl.SchedulingRequest(it.invocation.args[0] as InternalServer, 0) } + every { service.schedule(any()) } answers { ComputeService.SchedulingRequest(it.invocation.args[0] as ServiceServer, 0) } server.start() @@ -116,26 +97,26 @@ class InternalServerTest { @Test fun testStartDeletedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.DELETED + server.setState(ServerState.DELETED) assertThrows<IllegalStateException> { server.start() } } @Test fun testStartProvisioningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.PROVISIONING + server.setState(ServerState.PROVISIONING) server.start() @@ -144,13 +125,13 @@ class InternalServerTest { @Test fun testStartRunningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.RUNNING + server.setState(ServerState.RUNNING) server.start() @@ -159,12 +140,12 @@ class InternalServerTest { @Test fun testStopProvisioningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server, 0) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) + val request = ComputeService.SchedulingRequest(server, 0) every { service.schedule(any()) } returns request @@ -177,13 +158,13 @@ class InternalServerTest { @Test fun testStopTerminatedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.TERMINATED + server.setState(ServerState.TERMINATED) server.stop() assertEquals(ServerState.TERMINATED, server.state) @@ -191,13 +172,13 @@ class InternalServerTest { @Test fun testStopDeletedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.DELETED + server.setState(ServerState.DELETED) server.stop() assertEquals(ServerState.DELETED, server.state) @@ -205,29 +186,29 @@ class InternalServerTest { @Test fun testStopRunningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>() + val service = mockk<ComputeService>() val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) val host = mockk<Host>(relaxUnitFun = true) - server.state = ServerState.RUNNING + server.setState(ServerState.RUNNING) server.host = host server.stop() yield() - coVerify { host.stop(server) } + verify { host.stop(server) } } @Test fun testDeleteProvisioningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) + val service = mockk<ComputeService>(relaxUnitFun = true) val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server, 0) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) + val request = ComputeService.SchedulingRequest(server, 0) every { service.schedule(any()) } returns request @@ -241,13 +222,13 @@ class InternalServerTest { @Test fun testDeleteTerminatedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) + val service = mockk<ComputeService>(relaxUnitFun = true) val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.TERMINATED + server.setState(ServerState.TERMINATED) server.delete() assertEquals(ServerState.DELETED, server.state) @@ -257,13 +238,13 @@ class InternalServerTest { @Test fun testDeleteDeletedServer() = runSimulation { - val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) + val service = mockk<ComputeService>(relaxUnitFun = true) val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) - server.state = ServerState.DELETED + server.setState(ServerState.DELETED) server.delete() assertEquals(ServerState.DELETED, server.state) @@ -271,24 +252,24 @@ class InternalServerTest { @Test fun testDeleteRunningServer() = runSimulation { - val service = mockk<ComputeServiceImpl>(relaxUnitFun = true) + val service = mockk<ComputeService>(relaxUnitFun = true) val uid = UUID.randomUUID() val flavor = mockFlavor() val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf<String, Any>()) val host = mockk<Host>(relaxUnitFun = true) - server.state = ServerState.RUNNING + server.setState(ServerState.RUNNING) server.host = host server.delete() yield() - coVerify { host.delete(server) } + verify { host.delete(server) } verify { service.delete(server) } } - private fun mockFlavor(): InternalFlavor { - val flavor = mockk<InternalFlavor>() + private fun mockFlavor(): ServiceFlavor { + val flavor = mockk<ServiceFlavor>() every { flavor.name } returns "c5.large" every { flavor.uid } returns UUID.randomUUID() every { flavor.cpuCount } returns 2 @@ -296,8 +277,8 @@ class InternalServerTest { return flavor } - private fun mockImage(): InternalImage { - val image = mockk<InternalImage>() + private fun mockImage(): ServiceImage { + val image = mockk<ServiceImage>() every { image.name } returns "ubuntu-20.04" every { image.uid } returns UUID.randomUUID() return image diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt index 4608bf37..4af6f7ec 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt @@ -30,9 +30,9 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertThrows import org.opendc.compute.api.Server +import org.opendc.compute.service.HostView import org.opendc.compute.service.driver.HostModel import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.internal.HostView import org.opendc.compute.service.scheduler.filters.ComputeFilter import org.opendc.compute.service.scheduler.filters.DifferentHostFilter import org.opendc.compute.service.scheduler.filters.InstanceCountFilter diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt index a44ccc27..ec71f095 100644 --- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt @@ -65,9 +65,9 @@ import java.util.function.Supplier * @param optimize A flag to indicate to optimize the machine models of the virtual machines. */ public class SimHost( - override val uid: UUID, - override val name: String, - override val meta: Map<String, Any>, + private val uid: UUID, + private val name: String, + private val meta: Map<String, Any>, private val clock: InstantSource, private val machine: SimBareMetalMachine, private val hypervisor: SimHypervisor, @@ -87,11 +87,6 @@ public class SimHost( private val guests = HashMap<Server, Guest>() private val _guests = mutableListOf<Guest>() - override val instances: Set<Server> - get() = guests.keys - - override val state: HostState - get() = _state private var _state: HostState = HostState.DOWN set(value) { if (value != field) { @@ -100,7 +95,7 @@ public class SimHost( field = value } - override val model: HostModel = HostModel( + private val model: HostModel = HostModel( machine.model.cpus.sumOf { it.frequency }, machine.model.cpus.size, machine.model.memory.sumOf { it.size } @@ -123,6 +118,30 @@ public class SimHost( launch() } + override fun getUid(): UUID { + return uid + } + + override fun getName(): String { + return name + } + + override fun getModel(): HostModel { + return model + } + + override fun getMeta(): Map<String, *> { + return meta + } + + override fun getState(): HostState { + return _state + } + + override fun getInstances(): Set<Server> { + return guests.keys + } + override fun canFit(server: Server): Boolean { val sufficientMemory = model.memoryCapacity >= server.flavor.memorySize val enoughCpus = model.cpuCount >= server.flavor.cpuCount diff --git a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt index a496cc99..1734daf5 100644 --- a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt +++ b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt @@ -305,11 +305,11 @@ internal class SimHostTest { override val labels: Map<String, String> = emptyMap() override val meta: Map<String, Any> = emptyMap() - override suspend fun delete() { + override fun delete() { throw NotImplementedError() } - override suspend fun refresh() { + override fun reload() { throw NotImplementedError() } } @@ -320,11 +320,11 @@ internal class SimHostTest { override val labels: Map<String, String>, override val meta: Map<String, Any> ) : Image { - override suspend fun delete() { + override fun delete() { throw NotImplementedError() } - override suspend fun refresh() { + override fun reload() { throw NotImplementedError() } } @@ -343,16 +343,16 @@ internal class SimHostTest { override val launchedAt: Instant? = null - override suspend fun start() {} + override fun start() {} - override suspend fun stop() {} + override fun stop() {} - override suspend fun delete() {} + override fun delete() {} override fun watch(watcher: ServerWatcher) {} override fun unwatch(watcher: ServerWatcher) {} - override suspend fun refresh() {} + override fun reload() {} } } diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt index d7347327..4470c418 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt @@ -41,7 +41,9 @@ public class ComputeServiceProvisioningStep internal constructor( private val schedulingQuantum: Duration ) : ProvisioningStep { override fun apply(ctx: ProvisioningContext): AutoCloseable { - val service = ComputeService(ctx.dispatcher, scheduler(ctx), schedulingQuantum) + val service = ComputeService.builder(ctx.dispatcher, scheduler(ctx)) + .withQuantum(schedulingQuantum) + .build() ctx.registry.register(serviceDomain, ComputeService::class.java, service) return AutoCloseable { service.close() } |
