Files
ubicloud/spec/routes/web/kubernetes_cluster_spec.rb
Jeremy Evans 4b819d3cb2 Change all create_with_id to create
It hasn't been necessary to use create_with_id since
ebc79622df, in December 2024.

I have plans to introduce:

```ruby
def create_with_id(id, values)
  obj = new(values)
  obj.id = id
  obj.save_changes
end
```

This will make it easier to use the same id when creating
multiple objects.  The first step is removing the existing
uses of create_with_id.
2025-08-06 01:55:51 +09:00

384 lines
14 KiB
Ruby

# frozen_string_literal: true
require_relative "spec_helper"
RSpec.describe Clover, "Kubernetes" do
let(:user) { create_account }
let(:project) { user.create_project_with_default_policy("project-1") }
let(:project_wo_permissions) { user.create_project_with_default_policy("project-2", default_policy: nil) }
let(:kc) do
cluster = Prog::Kubernetes::KubernetesClusterNexus.assemble(
name: "myk8s",
version: "v1.32",
project_id: project.id,
private_subnet_id: PrivateSubnet.create(net6: "0::0", net4: "127.0.0.1", name: "mysubnet", location_id: Location::HETZNER_FSN1_ID, project_id: Config.kubernetes_service_project_id).id,
location_id: Location::HETZNER_FSN1_ID
).subject
services_lb = Prog::Vnet::LoadBalancerNexus.assemble(
cluster.private_subnet_id,
name: cluster.services_load_balancer_name,
algorithm: "hash_based",
# TODO: change the api to support LBs without ports
# The next two fields will be later modified by the sync_kubernetes_services label
# These are just set for passing the creation validations
src_port: 443,
dst_port: 6443,
health_check_endpoint: "/",
health_check_protocol: "tcp",
stack: LoadBalancer::Stack::IPV4
).subject
cluster.update(services_lb_id: services_lb.id)
cluster
end
let(:kc_no_perm) do
Prog::Kubernetes::KubernetesClusterNexus.assemble(
name: "not-my-k8s",
version: "v1.32",
project_id: project_wo_permissions.id,
private_subnet_id: PrivateSubnet.create(net6: "0::0", net4: "127.0.0.1", name: "othersubnet", location_id: Location::HETZNER_FSN1_ID, project_id: Config.kubernetes_service_project_id).id,
location_id: Location::HETZNER_FSN1_ID
).subject
end
before do
allow(Config).to receive(:kubernetes_service_project_id).and_return(Project.create(name: "UbicloudKubernetesService").id)
end
describe "unauthenticated" do
it "can not list without login" do
visit "#{project.path}/kubernetes-cluster"
expect(page.title).to eq("Ubicloud - Login")
end
it "can not create without login" do
visit "#{project.path}/kubernetes-cluster/create"
expect(page.title).to eq("Ubicloud - Login")
end
end
describe "authenticated" do
before do
login(user.email)
end
describe "list" do
it "works with 0 kubernetes clusters" do
visit "#{project.path}/kubernetes-cluster"
expect(page.title).to eq("Ubicloud - Kubernetes Clusters")
expect(page).to have_content "No Kubernetes Clusters"
expect(page).to have_content "Create Kubernetes Cluster"
click_link "Create Kubernetes Cluster"
expect(page.title).to eq("Ubicloud - Create Kubernetes Cluster")
end
it "lists existing permissible clusters" do
kc
kc_no_perm
visit "#{project.path}/kubernetes-cluster"
expect(page).to have_content "myk8s"
expect(page).to have_no_content "not-my-k8s"
expect(page).to have_content "Create Kubernetes Cluster"
end
it "doesn't show the create button without permission" do
project
AccessControlEntry.dataset.destroy
AccessControlEntry.create(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["KubernetesCluster:view"])
expect(KubernetesCluster.count).to eq(0)
visit "#{project.path}/kubernetes-cluster"
expect(page).to have_content("No Kubernetes Clusters")
expect(page).to have_no_content "Create Kubernetes Cluster"
kc
AccessControlEntry.dataset.destroy
AccessControlEntry.create(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["KubernetesCluster:view"])
expect(KubernetesCluster.count).to eq(1)
visit "#{project.path}/kubernetes-cluster"
expect(page).to have_content(kc.name)
expect(page).to have_no_content "No Kubernetes Clusters"
expect(page).to have_no_content "Create Kubernetes Cluster"
end
end
describe "create" do
before do
kc
visit "#{project.path}/kubernetes-cluster/create"
expect(page.title).to eq("Ubicloud - Create Kubernetes Cluster")
end
it "cannot create kubernetes cluster when location does not exist" do
fill_in "Cluster Name", with: "cannotcreate"
choose option: 3
find('select#worker_nodes option[value="4"]:not([disabled])').select_option
choose option: Location::LEASEWEB_WDC02_UBID
Location[Location::LEASEWEB_WDC02_ID].destroy
click_button "Create"
expect(page.title).to eq("Ubicloud - ResourceNotFound")
expect(page.status_code).to eq(404)
expect(page).to have_content("ResourceNotFound")
end
it "can not create cluster if project has no valid payment method" do
expect(described_class).to receive(:authorized_project).with(user, project.id).and_return(project).at_least(:once)
expect(Config).to receive(:stripe_secret_key).and_return("secret_key").at_least(:once)
page.refresh
expect(page).to have_content "Project doesn't have valid billing information"
fill_in "Cluster Name", with: "dummyk8s"
choose option: Location::HETZNER_FSN1_UBID
choose option: 3
find('select#worker_nodes option[value="4"]:not([disabled])').select_option
click_button "Create"
expect(page.title).to eq("Ubicloud - Create Kubernetes Cluster")
expect(page).to have_content "Project doesn't have valid billing information"
end
it "can create new kubernetes cluster" do
fill_in "Cluster Name", with: "k8stest"
choose option: Location::HETZNER_FSN1_UBID
choose option: 3
find('select#worker_nodes option[value="4"]:not([disabled])').select_option
[1, 2, 4, 8].each do
expect(page).to have_content "#{it * 2} vCPUs / #{it * 8} GB RAM / #{it * 40} GB NVMe Storage"
end
click_button "Create"
expect(page.title).to eq("Ubicloud - k8stest")
expect(page).to have_flash_notice("'k8stest' will be ready in a few minutes")
expect(KubernetesCluster.count).to eq(2)
new_kc = KubernetesCluster[name: "k8stest"]
expect(new_kc.project_id).to eq(project.id)
expect(new_kc.cp_node_count).to eq(3)
expect(new_kc.nodepools.first.node_count).to eq(4)
expect(new_kc.private_subnet.name).to eq("#{new_kc.ubid}-subnet")
end
it "can not create kubernetes cluster with invalid name" do
fill_in "Cluster Name", with: "invalid name"
choose option: Location::HETZNER_FSN1_UBID
choose option: 3
find('select#worker_nodes option[value="4"]:not([disabled])').select_option
click_button "Create"
expect(page.title).to eq("Ubicloud - Create Kubernetes Cluster")
expect(page).to have_content "Kubernetes cluster name must only contain lowercase"
expect((find "input[name=name]")["value"]).to eq("invalid name")
end
it "can not create kubernetes cluster with same name in same project & location" do
fill_in "Cluster Name", with: "myk8s"
choose option: Location::HETZNER_FSN1_UBID
choose option: 3
find('select#worker_nodes option[value="4"]:not([disabled])').select_option
click_button "Create"
expect(page.title).to eq("Ubicloud - Create Kubernetes Cluster")
expect(page).to have_flash_error("project_id and location_id and name is already taken")
end
it "can not select invisible location" do
expect { choose option: Location::GITHUB_RUNNERS_UBID }.to raise_error Capybara::ElementNotFound
end
it "can not create kubernetes cluster in a project when does not have permissions" do
visit "#{project_wo_permissions.path}/kubernetes-cluster/create"
expect(page.title).to eq("Ubicloud - Forbidden")
expect(page.status_code).to eq(403)
expect(page).to have_content "Forbidden"
end
end
describe "show" do
it "can show kubernetes cluster details" do
kc
visit "#{project.path}/kubernetes-cluster"
expect(page.title).to eq("Ubicloud - Kubernetes Clusters")
expect(page).to have_content kc.name
click_link kc.name, href: "#{project.path}#{kc.path}"
expect(page.title).to eq("Ubicloud - #{kc.name}")
expect(page).to have_content kc.name
expect(page).to have_content kc.ubid
expect(page).to have_content kc.display_location
expect(page).to have_content kc.version
kc.add_cp_vm(create_vm(name: "cp1"))
kc.add_cp_vm(create_vm(name: "cp2"))
kn = Prog::Kubernetes::KubernetesNodepoolNexus.assemble(
name: "kn",
node_count: 2,
kubernetes_cluster_id: kc.id
).subject
kn.add_vm(create_vm(name: "node1"))
kc.reload
page.refresh
expect(page).to have_content "cp1"
expect(page).to have_content "cp2"
expect(page).to have_content "node1"
expect(kc.display_state).to eq("creating")
expect(page.body).to include "auto-refresh hidden"
expect(page.body).to include "creating"
expect(page).to have_content "Waiting for cluster to be ready..."
expect(page).to have_no_content "Download"
kc.strand.update(label: "wait")
kn.strand.update(label: "wait")
page.refresh
expect(page.body).not_to include "auto-refresh hidden"
expect(page.body).to include "running"
expect(page).to have_no_content "Waiting for cluster to be ready..."
expect(page).to have_content "Download"
kc.incr_destroy
kc.reload
expect(kc.display_state).to eq("deleting")
page.refresh
expect(page.body).to include "deleting"
expect(page.body).to include "auto-refresh hidden"
end
it "works with ubid" do
visit "#{project.path}/location/#{kc.display_location}/kubernetes-cluster/#{kc.ubid}"
expect(page.title).to eq("Ubicloud - #{kc.name}")
expect(page).to have_content kc.name
end
it "does not show delete option without permissions" do
kc
AccessControlEntry.dataset.destroy
AccessControlEntry.create(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["KubernetesCluster:view"])
visit "#{project.path}#{kc.path}"
expect(page.title).to eq("Ubicloud - #{kc.name}")
expect(page).to have_content kc.name
expect(page).to have_content kc.ubid
expect(page).to have_content kc.display_location
expect(page).to have_no_content "Danger Zone"
end
it "raises forbidden when does not have permissions" do
visit "#{project_wo_permissions.path}#{kc_no_perm.path}"
expect(page.title).to eq("Ubicloud - Forbidden")
expect(page.status_code).to eq(403)
expect(page).to have_content "Forbidden"
end
it "raises not found when kubernetes cluster does not exist" do
visit "#{project.path}/location/eu-central-h1/kubernetes-cluster/blabla"
expect(page.title).to eq("Ubicloud - ResourceNotFound")
expect(page.status_code).to eq(404)
expect(page).to have_content "ResourceNotFound"
end
end
describe "kubeconfig" do
before do
kc
end
it "returns kubeconfig content for authorized users" do
expect(KubernetesCluster).to receive(:kubeconfig).and_return "kubeconfig content"
visit "#{project.path}#{kc.path}/kubeconfig"
expect(page.response_headers["Content-Type"]).to eq("text/plain")
expect(page.response_headers["Content-Disposition"]).to include("attachment; filename=\"#{kc.name}-kubeconfig.yaml\"")
expect(page.body).to eq("kubeconfig content")
end
it "raises forbidden error when user does not have permission" do
AccessControlEntry.dataset.destroy
AccessControlEntry.create(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["KubernetesCluster:view"])
visit "#{project.path}#{kc.path}/kubeconfig"
expect(page.status_code).to eq(403)
expect(page).to have_content("Forbidden")
end
it "raises not found when Kubernetes cluster does not exist" do
visit "#{project.path}/kubernetes-cluster/_nonexistent/kubeconfig"
expect(page.status_code).to eq(404)
expect(page).to have_content("ResourceNotFound")
end
it "returns proper content headers and content" do
expect(KubernetesCluster).to receive(:kubeconfig).and_return "mocked kubeconfig content"
visit "#{project.path}#{kc.path}/kubeconfig"
expect(page.response_headers["Content-Type"]).to eq("text/plain")
expect(page.response_headers["Content-Disposition"]).to include("attachment; filename=\"#{kc.name}-kubeconfig.yaml\"")
expect(page.body).to eq("mocked kubeconfig content")
end
it "does not allow unauthorized access" do
AccessControlEntry.dataset.destroy
visit "#{project.path}#{kc.path}/kubeconfig"
expect(page.status_code).to eq(403)
expect(page).to have_content("Forbidden")
end
end
describe "delete" do
it "can delete kubernetes cluster" do
visit "#{project.path}#{kc.path}"
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
# UI tests run without a JavaScript enginer.
btn = find "#kc-delete-#{kc.ubid} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(SemSnap.new(kc.id).set?("destroy")).to be true
end
it "can not delete kubernetes cluster when does not have permissions" do
# Give permission to view, so we can see the detail page
AccessControlEntry.create(project_id: project_wo_permissions.id, subject_id: user.id, action_id: ActionType::NAME_MAP["KubernetesCluster:view"])
visit "#{project_wo_permissions.path}#{kc_no_perm.path}"
expect(page.title).to eq "Ubicloud - not-my-k8s"
expect { find ".delete-btn" }.to raise_error Capybara::ElementNotFound
end
end
end
end