Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces VirtualMachineFleet and VirtualMachineFleetLive #22

Merged
merged 1 commit into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Packages/VirtualMachine/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let package = Package(
.library(name: "VirtualMachineEditorService", targets: ["VirtualMachineEditorService"]),
.library(name: "VirtualMachineFactory", targets: ["VirtualMachineFactory"]),
.library(name: "VirtualMachineFleet", targets: ["VirtualMachineFleet"]),
.library(name: "VirtualMachineFleetLive", targets: ["VirtualMachineFleetLive"]),
.library(name: "VirtualMachineResourcesCopier", targets: ["VirtualMachineResourcesCopier"]),
.library(name: "VirtualMachineResourcesService", targets: ["VirtualMachineResourcesService"]),
.library(name: "VirtualMachineResourcesServiceEditor", targets: ["VirtualMachineResourcesServiceEditor"]),
Expand All @@ -33,9 +34,11 @@ let package = Package(
.target(name: "VirtualMachineFactory", dependencies: [
"VirtualMachine"
]),
.target(name: "VirtualMachineFleet", dependencies: [
.target(name: "VirtualMachineFleet"),
.target(name: "VirtualMachineFleetLive", dependencies: [
"VirtualMachine",
"VirtualMachineFactory",
"VirtualMachineFleet",
.product(name: "LogConsumer", package: "Logging")
]),
.target(name: "VirtualMachineResourcesCopier", dependencies: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,83 +1,7 @@
import Combine
import LogConsumer
import VirtualMachine
import VirtualMachineFactory

public final class VirtualMachineFleet {
public let isStarted: AnyPublisher<Bool, Never>

private let logger: LogConsumer
private let virtualMachineFactory: VirtualMachineFactory
private var activeTasks: [Task<(), Error>] = []
private let _isStarted = CurrentValueSubject<Bool, Never>(false)

public init(logger: LogConsumer, virtualMachineFactory: VirtualMachineFactory) {
self.logger = logger
self.virtualMachineFactory = virtualMachineFactory
self.isStarted = _isStarted.eraseToAnyPublisher()
}

public func start(numberOfMachines: Int) throws {
guard !_isStarted.value else {
return
}
_isStarted.value = true
let preferredName = try virtualMachineFactory.preferredVirtualMachineName
for index in 0 ..< numberOfMachines {
let name = preferredName + "-\(index + 1)"
startSequentiallyRunningVirtualMachines(named: name)
}
}

public func stop() {
guard _isStarted.value else {
return
}
_isStarted.value = false
for task in activeTasks {
task.cancel()
}
activeTasks = []
}
}

private extension VirtualMachineFleet {
private func startSequentiallyRunningVirtualMachines(named name: String) {
let task = Task {
while !Task.isCancelled {
try await runVirtualMachine(named: name)
}
}
activeTasks.append(task)
}

private func runVirtualMachine(named name: String) async throws {
let virtualMachine: VirtualMachine
do {
virtualMachine = try await virtualMachineFactory.makeVirtualMachine(named: name)
} catch {
logger.error("Could not create virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
try await withTaskCancellationHandler {
logger.info("Start virtual machine named \"%@\"", name)
do {
try await virtualMachine.start()
logger.info("Did stop virtual machine named \"%@\"", name)
} catch {
logger.info("Could not start virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
} onCancel: {
Task.detached(priority: .high) {
self.logger.info("Stop virtual machine named \"%@\"", name)
do {
try await virtualMachine.stop()
} catch {
self.logger.info("Could not stop virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
}
}
}
public protocol VirtualMachineFleet {
var isStarted: AnyPublisher<Bool, Never> { get }
func start(numberOfMachines: Int) throws
func stop()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Combine
import LogConsumer
import VirtualMachine
import VirtualMachineFactory
import VirtualMachineFleet

public final class VirtualMachineFleetLive: VirtualMachineFleet {
public let isStarted: AnyPublisher<Bool, Never>

private let logger: LogConsumer
private let virtualMachineFactory: VirtualMachineFactory
private var activeTasks: [Task<(), Error>] = []
private let _isStarted = CurrentValueSubject<Bool, Never>(false)

public init(logger: LogConsumer, virtualMachineFactory: VirtualMachineFactory) {
self.logger = logger
self.virtualMachineFactory = virtualMachineFactory
self.isStarted = _isStarted.eraseToAnyPublisher()
}

public func start(numberOfMachines: Int) throws {
guard !_isStarted.value else {
return
}
_isStarted.value = true
let preferredName = try virtualMachineFactory.preferredVirtualMachineName
for index in 0 ..< numberOfMachines {
let name = preferredName + "-\(index + 1)"
startSequentiallyRunningVirtualMachines(named: name)
}
}

public func stop() {
guard _isStarted.value else {
return
}
_isStarted.value = false
for task in activeTasks {
task.cancel()
}
activeTasks = []
}
}

private extension VirtualMachineFleetLive {
private func startSequentiallyRunningVirtualMachines(named name: String) {
let task = Task {
while !Task.isCancelled {
try await runVirtualMachine(named: name)
}
}
activeTasks.append(task)
}

private func runVirtualMachine(named name: String) async throws {
let virtualMachine: VirtualMachine
do {
virtualMachine = try await virtualMachineFactory.makeVirtualMachine(named: name)
} catch {
logger.error("Could not create virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
try await withTaskCancellationHandler {
logger.info("Start virtual machine named \"%@\"", name)
do {
try await virtualMachine.start()
logger.info("Did stop virtual machine named \"%@\"", name)
} catch {
logger.info("Could not start virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
} onCancel: {
Task.detached(priority: .high) {
self.logger.info("Stop virtual machine named \"%@\"", name)
do {
try await virtualMachine.stop()
} catch {
self.logger.info("Could not stop virtual machine named \"%@\": %@", name, error.localizedDescription)
throw error
}
}
}
}
}
3 changes: 2 additions & 1 deletion Tartelet/Sources/CompositionRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import TartVirtualMachineSourceNameRepository
import VirtualMachineEditorService
import VirtualMachineFactory
import VirtualMachineFleet
import VirtualMachineFleetLive
import VirtualMachineResourcesCopier
import VirtualMachineResourcesService
import VirtualMachineResourcesServiceEditor
Expand All @@ -34,7 +35,7 @@ import VirtualMachineSourceNameRepository
enum CompositionRoot {
static let dock = Dock(showAppInDock: showAppInDockPublisher.rawValue)

static let fleet = VirtualMachineFleet(
static let fleet: VirtualMachineFleet = VirtualMachineFleetLive(
logger: logger(withCategory: .virtualMachine),
virtualMachineFactory: fleetVirtualMachineFactory
)
Expand Down
2 changes: 2 additions & 0 deletions project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ targets:
product: VirtualMachineFactory
- package: VirtualMachine
product: VirtualMachineFleet
- package: VirtualMachine
product: VirtualMachineFleetLive
- package: VirtualMachine
product: VirtualMachineEditorService
- package: VirtualMachine
Expand Down
Loading