From 8ea6fe46b5737b9957b5812dfd320ffeb419e46e Mon Sep 17 00:00:00 2001 From: Varik Matevosyan Date: Sat, 11 May 2024 14:23:29 -0700 Subject: [PATCH] Add support for custom cached images for Lantern --- config.rb | 28 ++++++++------ lib/hosting/gcp_apis.rb | 23 ++++++++++- lib/option.rb | 5 --- misc/misc_operations.rb | 47 +++++++++++++++++++++++ model/lantern/lantern_server.rb | 1 + prog/gcp_vm/nexus.rb | 2 +- prog/lantern/lantern_server_nexus.rb | 2 +- rhizome/lantern/bin/configure | 11 ++++-- spec/lib/hosting/gcp_apis_spec.rb | 22 +++++++++++ spec/model/lantern/lantern_server_spec.rb | 5 +++ spec/prog/gcp_vm/nexus_spec.rb | 4 +- 11 files changed, 124 insertions(+), 26 deletions(-) diff --git a/config.rb b/config.rb index bd9080e57..53a2d1a2a 100644 --- a/config.rb +++ b/config.rb @@ -133,17 +133,6 @@ def self.e2e_test? optional :ubicloud_images_blob_storage_secret_key, string, clear: true optional :ubicloud_images_blob_storage_certs, string - # Lantern - override :lantern_top_domain, "db.lantern.dev", string - override :lantern_dns_email, "varik@lantern.dev", string - override :lantern_default_version, "0.2.5", string - override :lantern_extras_default_version, "0.1.5", string - override :lantern_minor_default_version, "2", string - override :lantern_backup_bucket, "walg-dev-backups" - override :e2e_test, "0" - override :backup_retention_days, 7, int - optional :lantern_backend_database_url, string - # GCP override :gcp_project_id, "lantern-development", string override :gcp_compute_service_account, "339254316100-compute@developer.gserviceaccount.com", string @@ -154,6 +143,23 @@ def self.e2e_test? optional :prom_password, string override :gcr_image, "gcr.io/ringed-griffin-394922/lantern-bitnami" + # Lantern + override :lantern_top_domain, "db.lantern.dev", string + override :lantern_dns_email, "varik@lantern.dev", string + override :lantern_default_version, "0.2.7", string + override :lantern_extras_default_version, "0.1.5", string + override :lantern_minor_default_version, "1", string + override :lantern_backup_bucket, "walg-dev-backups" + override :e2e_test, "0" + override :backup_retention_days, 7, int + optional :lantern_backend_database_url, string + + # To use default ubuntu image pass these env vars to overwrite + # LANTERN_GCP_IMAGE=projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20240319 + # LANTERN_GCP_IMAGE_CACHED=false + override :lantern_gcp_image, "projects/#{gcp_project_id}/global/images/ubuntu-lantern-#{lantern_default_version.tr(".", "-")}-extras-#{lantern_extras_default_version.tr(".", "-")}-minor-#{lantern_minor_default_version}", string + override :lantern_gcp_image_cached, true, bool + # Cloudflare optional :cf_token, string optional :cf_zone_id, string diff --git a/lib/hosting/gcp_apis.rb b/lib/hosting/gcp_apis.rb index c52983a36..ef68c786e 100644 --- a/lib/hosting/gcp_apis.rb +++ b/lib/hosting/gcp_apis.rb @@ -42,7 +42,7 @@ def wait_for_operation(zone, operation) connection = Excon.new(@host[:connection_string], headers: @host[:headers]) loop do - response = connection.post(path: "/compute/v1/projects/#{@project}/zones/#{zone}/operations/#{operation}/wait", expects: [200]) + response = connection.post(path: "/compute/v1/projects/#{@project}/#{(zone == "global") ? "" : "zones/"}#{zone}/operations/#{operation}/wait", expects: [200]) body = JSON.parse(response.body) break unless body["status"] != "DONE" rescue Excon::Error::Timeout @@ -72,7 +72,7 @@ def create_vm(name, zone, image, ssh_key, user, machine_type, disk_size_gb, labe initializeParams: { diskSizeGb: disk_size_gb, diskType: "projects/#{@project}/zones/#{zone}/diskTypes/pd-ssd", - sourceImage: "projects/ubuntu-os-cloud/global/images/#{image}" + sourceImage: image }, mode: "READ_WRITE", type: "PERSISTENT" @@ -330,4 +330,23 @@ def allow_bucket_usage_by_prefix(service_account_email, bucket_name, prefix) Hosting::GcpApis.check_errors(response) end + + def create_image(name:, vm_name:, zone:, description: "", family: "lantern-ubuntu") + connection = Excon.new(@host[:connection_string], headers: @host[:headers]) + body = { + kind: "compute#image", + description: description, + name: name, + family: family, + sourceDisk: "projects/#{@project}/zones/#{zone}/disks/#{vm_name}", + storageLocations: [ + "us" + ] + } + + response = connection.post(path: "/compute/beta/projects/#{@project}/global/images", body: JSON.dump(body), expects: 200) + Hosting::GcpApis.check_errors(response) + data = JSON.parse(response.body) + wait_for_operation("global", data["id"]) + end end diff --git a/lib/option.rb b/lib/option.rb index 7c1f72cfa..2af73fb9d 100644 --- a/lib/option.rb +++ b/lib/option.rb @@ -22,11 +22,6 @@ def self.lantern_locations_for_provider(provider) Option::Locations.select { _1.provider.name == provider } end - BootImage = Struct.new(:name, :display_name) - BootImages = [ - ["ubuntu-2204-jammy-v20240319", "Ubuntu Jammy 22.04 LTS (GCP)"] - ].map { |args| BootImage.new(*args) }.freeze - VmSize = Struct.new(:name, :family, :vcpu, :memory, :storage_size_gib) do alias_method :display_name, :name end diff --git a/misc/misc_operations.rb b/misc/misc_operations.rb index 69288750e..1c2930a2e 100644 --- a/misc/misc_operations.rb +++ b/misc/misc_operations.rb @@ -161,4 +161,51 @@ def self.add_lantern_doctor_to_all end } end + + def self.create_image(name, description, image_tag, vm: nil) + gcp_api = Hosting::GcpApis.new + container_image = "#{Config.gcr_image}:#{image_tag}" + + if vm.nil? + vm = Prog::GcpVm::Nexus.assemble_with_sshable("lantern", Project.first.id, name: "imagecreation-machine", storage_size_gib: 10) + + # wait vm available + loop do + break if Strand[vm.id].label == "wait" + sleep 10 + end + + vm = GcpVm[vm.id] + puts "VM Created" + end + + key_data = vm.sshable.keys.map(&:private_key) + Util.rootish_ssh(vm.sshable.host, "lantern", key_data, < /tmp/get-docker.sh +chmod +x /tmp/get-docker.sh +/tmp/get-docker.sh +rm -rf /tmp/get-docker.sh +sudo sed -i 's/ulimit -Hn/ulimit -n/' /etc/init.d/docker +sudo service docker restart +echo #{Config.gcp_creds_gcr_b64} | base64 -d | sudo docker login -u _json_key --password-stdin https://gcr.io +sudo docker pull #{container_image} +sudo docker logout +history -cw +SH + puts "Dependencies installed" + + vm.incr_stop_vm + # wait vm stopped + loop do + break if GcpVm[vm.id].display_state == "stopped" + sleep 10 + end + + puts "VM stopped creating image" + gcp_api.create_image(name: name, vm_name: vm.name, zone: "#{vm.location}-a", description: description) + puts "Image created" + vm.incr_destroy + end end diff --git a/model/lantern/lantern_server.rb b/model/lantern/lantern_server.rb index 13e53cef7..e633e831b 100644 --- a/model/lantern/lantern_server.rb +++ b/model/lantern/lantern_server.rb @@ -101,6 +101,7 @@ def configure_hash JSON.generate({ enable_coredumps: true, + skip_deps: Config.lantern_gcp_image_cached, org_id: resource.org_id, instance_id: resource.name, instance_type: instance_type, diff --git a/prog/gcp_vm/nexus.rb b/prog/gcp_vm/nexus.rb index 8b7bc3316..1c7167bf7 100644 --- a/prog/gcp_vm/nexus.rb +++ b/prog/gcp_vm/nexus.rb @@ -18,7 +18,7 @@ class Prog::GcpVm::Nexus < Prog::Base semaphore :destroy, :start_vm, :stop_vm, :update_storage, :update_size def self.assemble(public_key, project_id, name: nil, size: "n1-standard-2", - unix_user: "lantern", location: "us-central1", boot_image: "ubuntu-2204-jammy-v20240319", + unix_user: "lantern", location: "us-central1", boot_image: Config.lantern_gcp_image, storage_size_gib: nil, arch: "x64", labels: {}) unless (project = Project[project_id]) diff --git a/prog/lantern/lantern_server_nexus.rb b/prog/lantern/lantern_server_nexus.rb index e220f6718..442e5b334 100644 --- a/prog/lantern/lantern_server_nexus.rb +++ b/prog/lantern/lantern_server_nexus.rb @@ -33,7 +33,7 @@ def self.assemble( location: resource.location, size: target_vm_size, storage_size_gib: target_storage_size_gib, - boot_image: "ubuntu-2204-jammy-v20240319", + boot_image: Config.lantern_gcp_image, labels: {"parent" => resource.name} ) diff --git a/rhizome/lantern/bin/configure b/rhizome/lantern/bin/configure index 66511249b..65ddc3abb 100755 --- a/rhizome/lantern/bin/configure +++ b/rhizome/lantern/bin/configure @@ -123,15 +123,18 @@ def setup_initial_compose_file end end -install_dependencies -puts "dependencies installed" +if $configure_hash[:skip_deps].nil? + install_dependencies + puts "dependencies installed" + configure_gcr($configure_hash["gcp_creds_gcr_b64"], $configure_hash["container_image"]) + puts "GCR repo ready" +end + setup_fs puts "filesystem ready" setup_env puts ".env setted up" setup_initial_compose_file puts "docker-compose.yaml ready" -configure_gcr($configure_hash["gcp_creds_gcr_b64"], $configure_hash["container_image"]) -puts "GCR repo ready" run_database($configure_hash["container_image"]) puts "database ready" diff --git a/spec/lib/hosting/gcp_apis_spec.rb b/spec/lib/hosting/gcp_apis_spec.rb index 388633272..8d50611b2 100644 --- a/spec/lib/hosting/gcp_apis_spec.rb +++ b/spec/lib/hosting/gcp_apis_spec.rb @@ -288,6 +288,14 @@ end describe "#wait_for_operation" do + it "waits for global operation to be done at first attempt" do + stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: JSON.dump({}), headers: {"Content-Type" => "application/json"}) + stub_request(:post, "https://compute.googleapis.com/compute/v1/projects/test-project/global/operations/test-op/wait").to_return(status: 200, body: JSON.dump({"status" => "DONE"}), headers: {"Content-Type" => "application/json"}) + + api = described_class.new + expect { api.wait_for_operation("global", "test-op") }.not_to raise_error + end + it "waits for operation to be done at first attempt" do stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: JSON.dump({}), headers: {"Content-Type" => "application/json"}) stub_request(:post, "https://compute.googleapis.com/compute/v1/projects/test-project/zones/us-central1-a/operations/test-op/wait").to_return(status: 200, body: JSON.dump({"status" => "DONE"}), headers: {"Content-Type" => "application/json"}) @@ -316,5 +324,19 @@ expect { api.wait_for_operation("us-central1-a", "test-op") }.not_to raise_error end end + + describe "#create_image" do + it "cerates image" do + stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: JSON.dump({}), headers: {"Content-Type" => "application/json"}) + stub_request(:post, "https://compute.googleapis.com/compute/beta/projects/test-project/global/images") + .with( + body: "{\"kind\":\"compute#image\",\"description\":\"test-desc\",\"name\":\"test-name\",\"family\":\"lantern-ubuntu\",\"sourceDisk\":\"projects/test-project/zones/us-central1-a/disks/inst-name\",\"storageLocations\":[\"us\"]}" + ) + .to_return(status: 200, body: JSON.dump({"id" => "test-op"}), headers: {}) + stub_request(:post, "https://compute.googleapis.com/compute/v1/projects/test-project/global/operations/test-op/wait").to_return(status: 200, body: JSON.dump({"status" => "DONE"}), headers: {"Content-Type" => "application/json"}) + api = described_class.new + expect { api.create_image(name: "test-name", vm_name: "inst-name", zone: "us-central1-a", description: "test-desc") }.not_to raise_error + end + end end end diff --git a/spec/model/lantern/lantern_server_spec.rb b/spec/model/lantern/lantern_server_spec.rb index 2c34f4fbc..61d8ccae7 100644 --- a/spec/model/lantern/lantern_server_spec.rb +++ b/spec/model/lantern/lantern_server_spec.rb @@ -240,6 +240,7 @@ db_user_password: "pwd123", superuser_password: "pwd1234", restore_target: nil) + expect(Config).to receive(:lantern_gcp_image_cached).and_return(false).at_least(:once) expect(Config).to receive(:prom_password).and_return("pwd123").at_least(:once) expect(Config).to receive(:gcp_creds_gcr_b64).and_return("test-creds").at_least(:once) expect(Config).to receive(:gcp_creds_logging_b64).and_return("test-creds").at_least(:once) @@ -254,6 +255,7 @@ walg_conf = timeline.generate_walg_config expected_conf = JSON.generate({ enable_coredumps: true, + skip_deps: false, org_id: resource.org_id, instance_id: resource.name, instance_type: lantern_server.standby? ? "reader" : "writer", @@ -313,6 +315,7 @@ walg_conf = timeline.generate_walg_config expected_conf = JSON.generate({ enable_coredumps: true, + skip_deps: true, org_id: resource.org_id, instance_id: resource.name, instance_type: lantern_server.standby? ? "reader" : "writer", @@ -370,6 +373,7 @@ walg_conf = timeline.generate_walg_config expected_conf = JSON.generate({ enable_coredumps: true, + skip_deps: true, org_id: resource.org_id, instance_id: resource.name, instance_type: lantern_server.standby? ? "reader" : "writer", @@ -428,6 +432,7 @@ walg_conf = timeline.generate_walg_config expected_conf = JSON.generate({ enable_coredumps: true, + skip_deps: true, org_id: resource.org_id, instance_id: resource.name, instance_type: lantern_server.standby? ? "reader" : "writer", diff --git a/spec/prog/gcp_vm/nexus_spec.rb b/spec/prog/gcp_vm/nexus_spec.rb index 043cd080c..c95ed6e80 100644 --- a/spec/prog/gcp_vm/nexus_spec.rb +++ b/spec/prog/gcp_vm/nexus_spec.rb @@ -12,7 +12,7 @@ let(:st) { Strand.new } let(:gcp_vm) { - vm = GcpVm.new_with_id(family: "standard", cores: 1, name: "dummy-vm", arch: "x64", location: "us-central1", storage_size_gib: 50) + vm = GcpVm.new_with_id(family: "standard", cores: 1, name: "dummy-vm", arch: "x64", location: "us-central1", storage_size_gib: 50, boot_image: Config.lantern_gcp_image) vm } let(:prj) { Project.create_with_id(name: "default", provider: "gcp").tap { _1.associate_with_project(_1) } } @@ -71,7 +71,7 @@ gcp_api = instance_double(Hosting::GcpApis) expect(Hosting::GcpApis).to receive(:new).and_return(gcp_api) frame = {"labels" => {"parent" => "test-label"}} - expect(gcp_api).to receive(:create_vm).with("dummy-vm", "us-central1-a", nil, nil, nil, "standard-1", 50, labels: frame["labels"]) + expect(gcp_api).to receive(:create_vm).with("dummy-vm", "us-central1-a", gcp_vm.boot_image, nil, nil, "standard-1", 50, labels: frame["labels"]) expect(nx).to receive(:frame).and_return(frame) expect(nx.strand).to receive(:stack).and_return([frame]).at_least(:once) expect(nx.strand).to receive(:modified!).with(:stack).at_least(:once)