Skip to content

Fix deleted VMs: race condition in cleanup and invoice/payment guards for deleted VMs#121

Merged
v0l merged 4 commits intomasterfrom
copilot/fix-deleted-vms-issue
Apr 3, 2026
Merged

Fix deleted VMs: race condition in cleanup and invoice/payment guards for deleted VMs#121
v0l merged 4 commits intomasterfrom
copilot/fix-deleted-vms-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 2, 2026

VMs were being marked as deleted after being paid for. Two separate issues were identified and fixed.

Root Causes

1. TOCTOU Race Condition in check_vms()

check_vms() snapshots all VMs via list_vms(), identifies unpaid VMs (created == expires, age > 1hr) for deletion, then executes the deletions. If a payment is confirmed (Lightning callback / Revolut webhook) between the snapshot and the deletion step, vm.expires is updated in the DB—but the deletion proceeds anyway based on the stale snapshot.

2. No Deleted-VM Guard on Invoice Creation or Payment Processing

get_user_vm() called db.get_vm(id) with no deleted check, so users could call the renew endpoint on an already-deleted VM and receive a fresh invoice. Additionally, vm_payment_paid() had no handling for payments arriving after a VM was auto-deleted, meaning paying an outstanding invoice (e.g. a slow Revolut payment or any future long-timeout payment method) had no effect on the VM.

Fixes

Race condition (worker.rs)

Re-read each VM from the DB immediately before deleting it. Skip deletion if the VM has transitioned to paid state (created != expires) since the snapshot. Also adds an admin notification when the pre-deletion DB read fails.

match self.db.get_vm(vm.id).await {
    Ok(current_vm) if current_vm.created == current_vm.expires => {
        // Still unpaid — delete
        self.provisioner.delete_vm(vm.id).await?;
    }
    Ok(_) => {
        info!("VM {} was paid since last check, skipping deletion", vm.id);
    }
    Err(e) => {
        error!("Failed to re-read VM {} before deletion: {}", vm.id, e);
        self.queue_admin_notification(...).await;
    }
}

Invoice creation guard (routes.rs)

get_user_vm() now rejects deleted VMs with "VM not found", preventing new invoices from being generated for them.

Payment processing — un-delete on payment (mysql.rs)

vm_payment_paid() now atomically sets deleted = 0 alongside the expiry extension in a single UPDATE. If a payment arrives for a VM that was auto-deleted (e.g. a slow payment from Revolut or a future long-timeout payment method), the VM is restored and the subsequent CheckVm job will re-spawn it on the host.

UPDATE vm SET expires = TIMESTAMPADD(SECOND, ?, expires), deleted = 0 WHERE id = ?

Mock consistency (mock.rs)

MockDb::delete_vm() changed from hard-delete to soft-delete (deleted = true) to match MySQL behaviour. MockDb::vm_payment_paid() mirrors the un-delete-on-payment behaviour.

Tests

  • test_check_vms_deletes_unpaid_vm_after_one_hour — unpaid VMs older than 1hr are deleted
  • test_check_vms_skips_unpaid_vm_within_one_hour — unpaid VMs younger than 1hr are not deleted

Copilot AI assigned Copilot and v0l Apr 2, 2026
Copilot AI linked an issue Apr 2, 2026 that may be closed by this pull request
Re-read VM state from database before deletion in the unpaid VM cleanup
loop. If a payment was confirmed between the initial list_vms() snapshot
and the deletion step, the VM will have expires != created and will be
skipped instead of deleted. Also adds admin notification when the
pre-deletion re-read fails.

Adds regression tests:
- test_check_vms_deletes_unpaid_vm_after_one_hour
- test_check_vms_skips_unpaid_vm_within_one_hour

Agent-Logs-Url: https://github.com/LNVPS/api/sessions/dfa9f0f9-6e61-4671-8747-e45fe763be13

Co-authored-by: v0l <1172179+v0l@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix issue with VMs incorrectly marked as deleted Fix race condition causing paid VMs to be incorrectly deleted Apr 2, 2026
Copilot AI requested a review from v0l April 2, 2026 16:51
Copilot finished work on behalf of v0l April 2, 2026 16:51
@v0l
Copy link
Copy Markdown
Contributor

v0l commented Apr 2, 2026

@copilot in the example in prod, the VM was already older than 1hr how was somebody able to generate an invoice and pay for a deleted vm

- Add vm.deleted check in get_user_vm() so the renew endpoint rejects
  deleted VMs with "VM not found"
- Add deleted guard in vm_payment_paid() (MySQL): update vm.expires only
  when deleted = 0; roll back and error if the VM was already deleted
- Mirror the deleted guard in MockDb::vm_payment_paid()
- Change MockDb::delete_vm() from hard-delete to soft-delete (set
  deleted = true) to match MySQL behavior
- Update rollback tests that assumed hard-delete semantics

Agent-Logs-Url: https://github.com/LNVPS/api/sessions/dc786568-4b72-4ed2-b117-c34f9b25f2e9

Co-authored-by: v0l <1172179+v0l@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 2, 2026

@copilot in the example in prod, the VM was already older than 1hr how was somebody able to generate an invoice and pay for a deleted vm

Good catch. There were two separate paths where this could happen:

  1. Invoice created before deletion, paid after: A user generates an invoice within the 1-hour window (invoice has 10-min Lightning expiry). The worker deletes the VM after 1 hour. If the user holds an older invoice from before the deletion (or the payment callback is delayed), vm_payment_paid() had no guard against updating vm.expires on a deleted VM — it would silently succeed and even trigger a CheckVm job, causing the VM to be re-spawned.

  2. Invoice created after deletion: get_user_vm() called db.get_vm(id) with no deleted check, so users could call the renew endpoint on an already-deleted VM and get a fresh invoice.

Fixed in commit 105a959:

  • get_user_vm() now rejects deleted VMs with "VM not found"
  • vm_payment_paid() in MySQL now adds AND deleted = 0 to the UPDATE vm SET expires = ... query and rolls back with an error if the VM is deleted
  • MockDb::delete_vm() changed from hard-delete to soft-delete to match MySQL behaviour

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • thisdomain.doesnotexist.invalid
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1jjpz0ffu3/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0kdo4mk16p7jn3eritjefprt6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1qrohftoe1/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0oy18n1wj2v8spnvvcow5mgm6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1smxt4jlsr/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0p6hrjklx7bas98o3nhaubteq.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1tbtlo2yer/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0rivicw45p0a22zqv0c00rjdt.0oqsiul.rcgu.o pytbiux9qwbqlvtr7g.1vb3mdw.rcgu.o zm69irmlb4fyntaq4j.1vb3mdw.rcgu.o guj6xod14lnexaqdeo.1vb3mdw.rcgu.o jv6srd0eghejp3fshr.1vb3mdw.rcgu.o 0v5e9h53eyr5mkbr8u.1vb3mdw.rcgu.o 4t2sz9gcdmwhidc92j.1vb3mdw.rcgu.o 63se�� 80xp4pywg1ne0n73tqqdb.0xgb1rf.rcgu.o a0qkyha321iypwdjrvxg0.0xgb1rf.rcgu.o f/enum-ordinaliz/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/rustcao6etU/symbols.o ib 7f5b.rlib (dns block)
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0d6owhzexjmbqgt8pc4adt81r.0oqsiul.rcgu.o 64-REDACTED-linux-gnu/bin/gcc-ld/ld.lld /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0jtmr7qfy9q55u3gdfkbssq7l.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0kdo4mk16p7jn3eritjefprt6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0oy18n1wj2v8spnvvcow5mgm6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0p6hrjklx7bas98o3nhaubteq.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0rivicw45p0a22zqv0c00rjdt.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0s3hwx�� /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0tpxh1qs8y5fbspy3yabqp1mo.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0uf4amxagyqaq12pnm9qg8sc6.0oqsiul.rcgu.o tvet�� s9rddjh2l8p6aau.1g9gsi9.rcgu.o h3f2dcevhqrhvuo.1g9gsi9.rcgu.o 4c84bf47/build_s/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_admin-b59e6ad37974f308.ed8--noprofile if-bcf9f0b3b383acc -incompat lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/rustcao6etU/symbols.o lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.00jj2sk5q4wmwo7e3s4fugfu0.0oqsiul.rcgu.o (dns block)
  • zap.stream
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1jjpz0ffu3/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0kdo4mk16p7jn3eritjefprt6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1qrohftoe1/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0oy18n1wj2v8spnvvcow5mgm6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1smxt4jlsr/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0p6hrjklx7bas98o3nhaubteq.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b115580e23a2fbe8.1tbtlo2yer/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0rivicw45p0a22zqv0c00rjdt.0oqsiul.rcgu.o pytbiux9qwbqlvtr7g.1vb3mdw.rcgu.o zm69irmlb4fyntaq4j.1vb3mdw.rcgu.o guj6xod14lnexaqdeo.1vb3mdw.rcgu.o jv6srd0eghejp3fshr.1vb3mdw.rcgu.o 0v5e9h53eyr5mkbr8u.1vb3mdw.rcgu.o 4t2sz9gcdmwhidc92j.1vb3mdw.rcgu.o 63se�� 80xp4pywg1ne0n73tqqdb.0xgb1rf.rcgu.o a0qkyha321iypwdjrvxg0.0xgb1rf.rcgu.o f/enum-ordinaliz/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/rustcao6etU/symbols.o ib 7f5b.rlib (dns block)
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-ee1800b72dca3a03 --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0d6owhzexjmbqgt8pc4adt81r.0oqsiul.rcgu.o 64-REDACTED-linux-gnu/bin/gcc-ld/ld.lld /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0jtmr7qfy9q55u3gdfkbssq7l.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0kdo4mk16p7jn3eritjefprt6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0oy18n1wj2v8spnvvcow5mgm6.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0p6hrjklx7bas98o3nhaubteq.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0rivicw45p0a22zqv0c00rjdt.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0s3hwx�� /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0tpxh1qs8y5fbspy3yabqp1mo.0oqsiul.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.0uf4amxagyqaq12pnm9qg8sc6.0oqsiul.rcgu.o tvet�� s9rddjh2l8p6aau.1g9gsi9.rcgu.o h3f2dcevhqrhvuo.1g9gsi9.rcgu.o 4c84bf47/build_s/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_admin-b59e6ad37974f308.ed8--noprofile if-bcf9f0b3b383acc -incompat lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/rustcao6etU/symbols.o lib/rustlib/x86_/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-511cbf24413ecd39.00jj2sk5q4wmwo7e3s4fugfu0.0oqsiul.rcgu.o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Fix race condition causing paid VMs to be incorrectly deleted Fix deleted VMs: race condition in cleanup and invoice/payment guards for deleted VMs Apr 2, 2026
Copilot finished work on behalf of v0l April 2, 2026 17:03
.await?
.rows_affected();

if rows_affected == 0 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this rollback we should just un-delete the VM assuming its a new vm, because there might be other payment methods where payments can persist longer than our pre-defined "delete unpaid vms"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a6c0b5a. vm_payment_paid() now sets deleted = 0 alongside the expiry update in a single UPDATE, so a payment for a previously auto-deleted VM restores it. The subsequent CheckVm job will re-spawn it on the host. This applies to both MySQL and MockDb.

When vm_payment_paid() receives a payment for a deleted VM (e.g. auto-
deleted before a slow payment arrived), un-delete it and apply the
payment instead of rolling back with an error. The subsequent CheckVm
job will re-spawn it on the host. This handles payment methods with
longer timeouts (Revolut, future Stripe/Paypal) where payments can
persist beyond the auto-delete window.

Agent-Logs-Url: https://github.com/LNVPS/api/sessions/d9b462bf-2581-4ede-8eca-d02a6ab63ac5

Co-authored-by: v0l <1172179+v0l@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 2, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • thisdomain.doesnotexist.invalid
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-21b4b50c0deb615d /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-21b4b50c0deb615d --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.0zmivokgnz/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0g8rwbm5rgt21ahru3jihg7eg.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.0zqrrz72xe/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0geu81m9w8xkpkjj8lwer10vp.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.1kfgzz5xoj/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0iwehqbl7wt0w7sccurwd5epr.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.1m2vjftvbf/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0lkffbd23aqceao06306vtmxm.0by7wfs.rcgu.o dw6pwei5fgll121qum.004fq1t.rcgu.o 1665c7gt1lejbd0sk2.004fq1t.rcgu.o xc7v6iqq5uu25kxvl8.004fq1t.rcgu.o 7v46on6bddz3k4na2q.004fq1t.rcgu.o r1aif56xuqc6e9p2nl.004fq1t.rcgu.o 47l4y22lacrnip4o3i.004fq1t.rcgu.o fnro�� 8gmtoblwypcptcdn6s7di.1tqh3nl.rcgu.o c51iz9acw6cmwk3kd7w76.1tqh3nl.rcgu.o ndor/bin/as ib 59ce.rlib (dns block)
  • zap.stream
    • Triggering command: /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-21b4b50c0deb615d /home/REDACTED/work/api/api/target/debug/deps/lnvps_api-21b4b50c0deb615d --test-threads=1 /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.0zmivokgnz/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0g8rwbm5rgt21ahru3jihg7eg.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.0zqrrz72xe/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0geu81m9w8xkpkjj8lwer10vp.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.1kfgzz5xoj/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0iwehqbl7wt0w7sccurwd5epr.0by7wfs.rcgu.o /home/REDACTED/work/api/api/target/debug/deps/lnvps_db-b674fa84ab471f79.1m2vjftvbf/home/REDACTED/work/api/api/target/debug/deps/lnvps_api_common-6ec4026b90f41e22.0lkffbd23aqceao06306vtmxm.0by7wfs.rcgu.o dw6pwei5fgll121qum.004fq1t.rcgu.o 1665c7gt1lejbd0sk2.004fq1t.rcgu.o xc7v6iqq5uu25kxvl8.004fq1t.rcgu.o 7v46on6bddz3k4na2q.004fq1t.rcgu.o r1aif56xuqc6e9p2nl.004fq1t.rcgu.o 47l4y22lacrnip4o3i.004fq1t.rcgu.o fnro�� 8gmtoblwypcptcdn6s7di.1tqh3nl.rcgu.o c51iz9acw6cmwk3kd7w76.1tqh3nl.rcgu.o ndor/bin/as ib 59ce.rlib (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of v0l April 2, 2026 17:17
Copilot AI requested a review from v0l April 2, 2026 17:17
@v0l
Copy link
Copy Markdown
Contributor

v0l commented Apr 2, 2026

@isolkewo review this pls

@isolkewo
Copy link
Copy Markdown
Contributor

isolkewo commented Apr 2, 2026

LGTM! ✅

I've reviewed the implementation and it looks solid. The fixes address both issues mentioned in the PR description:

1. Race condition in check_vms(): ✅

  • Re-reads the VM from DB before deletion (lines 489-519 in worker.rs)
  • Skips deletion if VM transitioned to paid state
  • Adds admin notification on read failures

2. Deleted-VM guards: ✅

  • rejects deleted VMs (routes.rs)
  • un-deletes VMs on payment (mysql.rs line 805)
  • MockDB updated to match production behavior with soft deletes

The implementation handles the edge cases well, particularly the scenario where a payment arrives after a VM has been auto-deleted. The atomic UPDATE that sets both and ensures consistency.

Ready to merge once you're satisfied!

Copy link
Copy Markdown
Contributor

@isolkewo isolkewo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! The implementation correctly addresses both issues:

Race condition fix in check_vms():

  • Re-reads VM from DB before deletion to catch payments that arrived after the snapshot
  • Properly skips deletion for paid VMs
  • Includes error handling for DB read failures

Deleted-VM guards:

  • Invoice creation now rejects deleted VMs
  • Payment processing un-deletes VMs (atomically sets deleted=0 and extends expiry)
  • MockDB updated to match production soft-delete behavior

The atomic UPDATE in vm_payment_paid() is particularly good - it ensures consistency when payments arrive for auto-deleted VMs.

Ready to merge.

@v0l v0l marked this pull request as ready for review April 2, 2026 17:50
@v0l v0l merged commit 7b2f497 into master Apr 3, 2026
6 checks passed
@v0l v0l deleted the copilot/fix-deleted-vms-issue branch April 3, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deleted VM's

3 participants