ubicloud/kubernetes/csi/lib/ubi_csi/kubernetes_client.rb
mohi-kalantari 4c383de3ca Fix PVC recreation issue
Before this commit, we were recreating the PVC multiple times and
it was not idempotent. On certain cases it break the flow and pvc
would get stuck in a bad state.

With this change, by using pvc uid, we make sure we delete the pvc
once and if we are not the one recreating the pvc, we make sure we
have added the annotation to let the controller on the destination
node do the migration

Some changes were made to the kubernetes client to patch resources
instead of updating them which is simpler and is idempotent
2025-09-24 11:59:14 +02:00

107 lines
3.1 KiB
Ruby

# frozen_string_literal: true
require "open3"
require "yaml"
require_relative "errors"
module Csi
class KubernetesClient
include ServiceHelper
def initialize(req_id:, logger:)
@req_id = req_id
@logger = logger
end
def run_kubectl(*args, yaml_data: nil)
cmd = ["kubectl", *args]
stdin_data = yaml_data ? YAML.dump(yaml_data) : nil
output, status = run_cmd(*cmd, req_id: @req_id, stdin_data:)
unless status.success?
if output.strip.end_with?("not found")
raise ObjectNotFoundError, output
end
raise "Command failed: #{cmd.join(" ")}\nOutput: #{output}"
end
output
end
def yaml_load_kubectl(*)
YAML.safe_load(run_kubectl(*))
end
def get_node(name)
yaml_load_kubectl("get", "node", name, "-oyaml")
end
def get_node_ip(name)
node_yaml = get_node(name)
node_yaml.dig("status", "addresses", 0, "address")
end
def get_pv(name)
yaml_load_kubectl("get", "pv", name, "-oyaml")
end
def extract_node_from_pv(pv)
pv.dig("spec", "nodeAffinity", "required", "nodeSelectorTerms", 0, "matchExpressions", 0, "values", 0)
end
def create_pv(yaml_data)
run_kubectl("create", "-f", "-", yaml_data:)
end
def update_pv(yaml_data)
run_kubectl("apply", "-f", "-", yaml_data:)
end
def get_pvc(namespace, name)
yaml_load_kubectl("-n", namespace, "get", "pvc", name, "-oyaml")
end
def create_pvc(yaml_data)
run_kubectl("create", "-f", "-", yaml_data:)
end
def update_pvc(yaml_data)
run_kubectl("apply", "-f", "-", yaml_data:)
end
def delete_pvc(namespace, name)
run_kubectl("-n", namespace, "delete", "pvc", name, "--wait=false", "--ignore-not-found=true")
end
def patch_resource(resource, name, annotation_key, annotation_value, namespace: "")
patch = {metadata: {annotations: {annotation_key => annotation_value}}}.to_json
cmd = ["patch", resource, name, "--type=merge", "-p", patch]
cmd = ["-n", namespace] + cmd unless namespace.empty?
run_kubectl(*cmd)
end
# This function will first try to get the pvc in order to make sure pvc exists
def remove_pvc_finalizers(namespace, name)
get_pvc(namespace, name)
patch = {metadata: {finalizers: nil}}.to_json
run_kubectl("-n", namespace, "patch", "pvc", name, "--type=merge", "-p", patch)
rescue ObjectNotFoundError
end
def remove_pvc_annotation(namespace, name, annotation_key)
patch = {metadata: {annotations: {annotation_key => nil}}}.to_json
run_kubectl("-n", namespace, "patch", "pvc", name, "--type=merge", "-p", patch)
end
def node_schedulable?(name)
!get_node(name).dig("spec", "unschedulable")
end
def find_pv_by_volume_id(volume_id)
pv_list = yaml_load_kubectl("get", "pv", "-oyaml")
pv = pv_list["items"].find { |pv| pv.dig("spec", "csi", "volumeHandle") == volume_id }
raise ObjectNotFoundError, "PersistentVolume with volumeHandle '#{volume_id}' not found" unless pv
pv
end
end
end