Files
ubicloud/spec/routes/api/project/location/vm_spec.rb
Jeremy Evans d4bb9e8619 Allow access to objects in internal locations in web/api routes
However, do not allow creation of objects in internal locations
in web/api routes.

Show helpful error message if using an invalid location in the api.
This error message shows what the problem is, and the available
valid locations the user can use.
2025-03-25 11:48:42 -07:00

278 lines
11 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# frozen_string_literal: true
require_relative "../../spec_helper"
RSpec.describe Clover, "vm" do
let(:user) { create_account }
let(:project) { project_with_default_policy(user) }
let(:vm) do
vm = Prog::Vm::Nexus.assemble("dummy-public-key", project.id, name: "dummy-vm-1").subject
vm.update(ephemeral_net6: "2a01:4f8:173:1ed3:aa7c::/79")
vm.reload # without reload ephemeral_net6 is string and can't call .network
end
describe "unauthenticated" do
it "cannot perform authenticated operations" do
[
[:get, "/project/#{project.ubid}/location/#{vm.display_location}/vm"],
[:post, "/project/#{project.ubid}/location/#{vm.display_location}/vm/foo_name"],
[:delete, "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.name}"],
[:delete, "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.ubid}"],
[:get, "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.name}"],
[:get, "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.ubid}"]
].each do |method, path|
send method, path
expect(last_response).to have_api_error(401, "Please login to continue")
end
end
end
describe "authenticated" do
before do
login_api(user.email)
end
describe "list" do
it "success multiple" do
Prog::Vm::Nexus.assemble("dummy-public-key", project.id, name: "dummy-vm-2")
get "/project/#{project.ubid}/location/#{vm.display_location}/vm"
expect(last_response.status).to eq(200)
parsed_body = JSON.parse(last_response.body)
expect(parsed_body["items"].length).to eq(2)
expect(parsed_body["count"]).to eq(2)
end
it "success multiple location with pagination" do
Prog::Vm::Nexus.assemble("dummy-public-key", project.id, name: "dummy-vm-2")
Prog::Vm::Nexus.assemble("dummy-public-key", project.id, name: "dummy-vm-3")
Prog::Vm::Nexus.assemble("dummy-public-key", project.id, name: "dummy-vm-4", location_id: "e0865080-9a3d-8020-a812-f5817c7afe7f")
get "/project/#{project.ubid}/location/#{vm.display_location}/vm", {
order_column: "name",
start_after: "dummy-vm-1"
}
expect(last_response.status).to eq(200)
parsed_body = JSON.parse(last_response.body)
expect(parsed_body["items"].length).to eq(2)
expect(parsed_body["count"]).to eq(3)
end
it "ubid not exist" do
get "/project/#{project.ubid}/location/#{vm.display_location}/vm/_foo_ubid"
expect(last_response).to have_api_error(404, "Sorry, we couldnt find the resource youre looking for.")
end
it "location not exist" do
get "/project/#{project.ubid}/location/not-exist-location/vm"
expect(last_response).to have_api_error(404, "Validation failed for following path components: location")
end
end
describe "create" do
it "success" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "ubuntu-jammy"
}.to_json
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq("test-vm")
expect(Vm.first.ip4_enabled).to be false
end
it "success with private subnet" do
ps_id = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location[display_name: TEST_LOCATION].id).ubid
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "ubuntu-jammy",
private_subnet_id: ps_id,
enable_ip4: true
}.to_json
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq("test-vm")
expect(Vm.first.ip4_enabled).to be true
end
it "success with storage size" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "ubuntu-jammy",
storage_size: "40"
}.to_json
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq("test-vm")
end
it "boot image doesn't passed" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
enable_ip4: true
}.to_json
expect(last_response.status).to eq(200)
end
it "invalid name" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/MyVM", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
enable_ip4: true
}.to_json
expect(last_response).to have_api_error(400, "Validation failed for following fields: name", {"name" => "Name must only contain lowercase letters, numbers, and hyphens and have max length 63."})
end
it "invalid boot image" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "invalid-boot-image",
enable_ip4: true
}.to_json
expect(last_response).to have_api_error(400, "Validation failed for following fields: boot_image", {"boot_image" => "\"invalid-boot-image\" is not a valid boot image name. Available boot image names are: [\"ubuntu-noble\", \"ubuntu-jammy\", \"debian-12\", \"almalinux-9\"]"})
end
it "invalid vm size" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-gpu-6",
enable_ip4: true
}.to_json
expect(last_response).to have_api_error(400, "Validation failed for following fields: size", {"size" => "\"standard-gpu-6\" is not a valid virtual machine size. Available sizes: [\"standard-2\", \"standard-4\", \"standard-8\", \"standard-16\", \"standard-30\", \"standard-60\", \"burstable-1\", \"burstable-2\"]"})
end
it "success without vm_size" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
boot_image: "ubuntu-jammy",
enable_ip4: true
}.to_json
expect(last_response.status).to eq(200)
end
it "invalid ps id" do
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "ubuntu-jammy",
private_subnet_id: "invalid-ubid",
enable_ip4: true
}.to_json
expect(last_response).to have_api_error(400, "Validation failed for following fields: private_subnet_id", {"private_subnet_id" => "Private subnet with the given id \"invalid-ubid\" is not found in the location \"eu-central-h1\""})
end
it "invalid ps id in other location" do
ps_id = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: "e0865080-9a3d-8020-a812-f5817c7afe7f").ubid
post "/project/#{project.ubid}/location/#{TEST_LOCATION}/vm/test-vm", {
public_key: "ssh key",
unix_user: "ubi",
size: "standard-2",
boot_image: "ubuntu-jammy",
private_subnet_id: ps_id,
enable_ip4: true
}.to_json
expect(last_response).to have_api_error(400, "Validation failed for following fields: private_subnet_id", {"private_subnet_id" => "Private subnet with the given id \"#{ps_id}\" is not found in the location \"eu-central-h1\""})
end
end
describe "show" do
it "success" do
get "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.name}"
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq(vm.name)
end
it "success ubid" do
get "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.ubid}"
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)["name"]).to eq(vm.name)
end
it "not found" do
get "/project/#{project.ubid}/location/#{vm.display_location}/vm/not-exists-vm"
expect(last_response).to have_api_error(404, "Sorry, we couldnt find the resource youre looking for.")
end
end
describe "delete" do
it "success" do
delete "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.name}"
expect(last_response.status).to eq(204)
expect(SemSnap.new(vm.id).set?("destroy")).to be true
end
it "success ubid" do
delete "/project/#{project.ubid}/location/#{vm.display_location}/vm/#{vm.ubid}"
expect(last_response.status).to eq(204)
expect(SemSnap.new(vm.id).set?("destroy")).to be true
end
it "not exist" do
delete "/project/#{project.ubid}/location/#{vm.display_location}/vm/foo-name"
expect(last_response.status).to eq(204)
expect(SemSnap.new(vm.id).set?("destroy")).to be false
end
it "not exist ubid" do
delete "/project/#{project.ubid}/location/#{vm.display_location}/vm/_foo_ubid"
expect(last_response.status).to eq(204)
expect(SemSnap.new(vm.id).set?("destroy")).to be false
end
it "returns appropriate error message for accessing invalid location" do
delete "/project/#{project.ubid}/location/us-east-a3/vm/#{vm.ubid}"
expect(last_response.status).to eq(404)
expect(JSON.parse(last_response.body)).to eq("error" => {"code" => 404, "details" => {"location" => "Given location is not a valid location. Available locations: eu-central-h1, eu-north-h1, us-east-a2"}, "message" => "Validation failed for following path components: location", "type" => "InvalidLocation"})
expect(SemSnap.new(vm.id).set?("destroy")).to be false
end
it "returns appropriate error message for trying to create in internal location" do
post "/project/#{project.ubid}/location/github-runners/vm/test-vm", {
public_key: "ssh key"
}.to_json
expect(last_response.status).to eq(404)
expect(JSON.parse(last_response.body)).to eq("error" => {"code" => 404, "details" => {"location" => "Given location is not a valid location. Available locations: eu-central-h1, eu-north-h1, us-east-a2"}, "message" => "Validation failed for following path components: location", "type" => "InvalidLocation"})
expect(SemSnap.new(vm.id).set?("destroy")).to be false
end
end
end
end