From 118b289404732fe2390018c96512a1329741ef79 Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Wed, 23 Oct 2019 10:35:56 -0700 Subject: [PATCH] Add more tests --- lib/krane/global_deploy_task.rb | 9 +- lib/kubernetes-deploy/deploy_task.rb | 5 +- lib/kubernetes-deploy/kubernetes_resource.rb | 2 +- test/helpers/mock_resource.rb | 76 +++++++++++++++ test/helpers/resource_cache_test_helper.rb | 4 +- test/test_helper.rb | 2 +- .../resource_deployer_test.rb | 96 +++++++++++++++++++ .../resource_watcher_test.rb | 52 ---------- test/unit/resource_cache_test.rb | 17 ++++ 9 files changed, 197 insertions(+), 66 deletions(-) create mode 100644 test/helpers/mock_resource.rb create mode 100644 test/unit/kubernetes-deploy/resource_deployer_test.rb diff --git a/lib/krane/global_deploy_task.rb b/lib/krane/global_deploy_task.rb index c5b8a89a9..1e56e102c 100644 --- a/lib/krane/global_deploy_task.rb +++ b/lib/krane/global_deploy_task.rb @@ -29,18 +29,10 @@ class GlobalDeployTask # Initializes the deploy task # - # @param namespace [String] Kubernetes namespace # @param context [String] Kubernetes context - # @param logger [Object] Logger object (defaults to an instance of KubernetesDeploy::FormattedLogger) - # @param kubectl_instance [Kubectl] Kubectl instance - # @param bindings [Hash] Bindings parsed by KubernetesDeploy::BindingsParser # @param max_watch_seconds [Integer] Timeout in seconds # @param selector [Hash] Selector(s) parsed by KubernetesDeploy::LabelSelector # @param template_paths [Array] An array of template paths - # @param template_dir [String] Path to a directory with templates (deprecated) - # @param protected_namespaces [Array] Array of protected Kubernetes namespaces (defaults - # to KubernetesDeploy::DeployTask::PROTECTED_NAMESPACES) - # @param render_erb [Boolean] Enable ERB rendering def initialize(context:, max_watch_seconds: nil, selector: nil, template_paths: []) template_paths = template_paths.map { |path| File.expand_path(path) } @@ -81,6 +73,7 @@ def run!(verify_result: true, prune: true) logger.phase_heading("Deploying all resources") deploy!(resources, verify_result, prune) + StatsD.event("Deployment succeeded", "Successfully deployed all resources to #{context}", alert_type: "success", tags: statsd_tags << "status:success") diff --git a/lib/kubernetes-deploy/deploy_task.rb b/lib/kubernetes-deploy/deploy_task.rb index 5c5bb72c6..0c0038355 100644 --- a/lib/kubernetes-deploy/deploy_task.rb +++ b/lib/kubernetes-deploy/deploy_task.rb @@ -219,7 +219,7 @@ def resource_deployer selector: @selector, statsd_tags: statsd_tags, current_sha: @current_sha) end - def global_resource_names + def global_resource_kinds cluster_resource_discoverer.global_resource_kinds end @@ -291,6 +291,7 @@ def validate_globals(resources) end def check_initial_status(resources) + @task_config.global_kinds = global_resource_kinds.map(&:downcase) cache = ResourceCache.new(@task_config) KubernetesDeploy::Concurrency.split_across_threads(resources) { |r| r.sync(cache) } resources.each { |r| @logger.info(r.pretty_status) } @@ -309,7 +310,7 @@ def discover_resources current_sha: @current_sha, bindings: @bindings) do |r_def| crd = crds_by_kind[r_def["kind"]]&.first r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def, - statsd_tags: @namespace_tags, crd: crd, global_names: global_resource_names) + statsd_tags: @namespace_tags, crd: crd, global_names: global_resource_kinds) resources << r @logger.info(" - #{r.id}") end diff --git a/lib/kubernetes-deploy/kubernetes_resource.rb b/lib/kubernetes-deploy/kubernetes_resource.rb index 0bb47e51e..f49d4ad48 100644 --- a/lib/kubernetes-deploy/kubernetes_resource.rb +++ b/lib/kubernetes-deploy/kubernetes_resource.rb @@ -317,7 +317,7 @@ def debug_message(cause = nil, info_hash = {}) def fetch_events(kubectl) return {} unless exists? out, _err, st = kubectl.run("get", "events", "--output=go-template=#{Event.go_template_for(type, name)}", - log_failure: false) + log_failure: false, use_namespace: !global?) return {} unless st.success? event_collector = Hash.new { |hash, key| hash[key] = [] } diff --git a/test/helpers/mock_resource.rb b/test/helpers/mock_resource.rb new file mode 100644 index 000000000..58047eb86 --- /dev/null +++ b/test/helpers/mock_resource.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +MockResource = Struct.new(:id, :hits_to_complete, :status) do + def debug_message(*) + @debug_message + end + + def sync(_cache) + @hits ||= 0 + @hits += 1 + end + + def after_sync + end + + def type + "MockResource" + end + alias_method :kubectl_resource_type, :type + + def pretty_timeout_type + end + + def deploy_method + :apply + end + + def file_path + "/dev/null" + end + + def deploy_started_at=(_) + end + + def sensitive_template_content? + true + end + + def global? + false + end + + def deploy_succeeded? + status == "success" && hits_complete? + end + + def deploy_failed? + status == "failed" && hits_complete? + end + + def deploy_timed_out? + status == "timeout" && hits_complete? + end + + def timeout + hits_to_complete + end + + def sync_debug_info(_) + @debug_message = "Something went wrong" + end + + def pretty_status + "#{id} #{status} (#{@hits} hits)" + end + + def report_status_to_statsd(watch_time) + end + + private + + def hits_complete? + @hits ||= 0 + @hits >= hits_to_complete + end +end diff --git a/test/helpers/resource_cache_test_helper.rb b/test/helpers/resource_cache_test_helper.rb index fc0a13673..b9fbb1ec6 100644 --- a/test/helpers/resource_cache_test_helper.rb +++ b/test/helpers/resource_cache_test_helper.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true module ResourceCacheTestHelper - def stub_kind_get(kind, items: [], times: 1) + def stub_kind_get(kind, items: [], times: 1, use_namespace: true) stub_kubectl_response( "get", kind, "--chunk-size=0", resp: { items: items }, - kwargs: { attempts: 5, output_is_sensitive: false, use_namespace: true }, + kwargs: { attempts: 5, output_is_sensitive: false, use_namespace: use_namespace }, times: times, ) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 26cd2d563..c4f0e61fa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -224,7 +224,7 @@ def mock_output_stream end def task_config(context: KubeclientHelper::TEST_CONTEXT, namespace: @namespace, logger: @logger) - KubernetesDeploy::TaskConfig.new(context, namespace, logger) + @task_config ||= KubernetesDeploy::TaskConfig.new(context, namespace, logger) end def krane_black_box(command, args = "") diff --git a/test/unit/kubernetes-deploy/resource_deployer_test.rb b/test/unit/kubernetes-deploy/resource_deployer_test.rb new file mode 100644 index 000000000..5cfd9c34b --- /dev/null +++ b/test/unit/kubernetes-deploy/resource_deployer_test.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true +require 'test_helper' +require 'kubernetes-deploy/resource_deployer' + +class ResourceDeployerTest < KubernetesDeploy::TestCase + def test_deploy_prune_builds_whitelist + whitelist_kind = "fake_kind" + resource = build_mock_resource + KubernetesDeploy::Kubectl.any_instance.expects(:run).with do |*args| + args.include?("--prune-whitelist=#{whitelist_kind}") + end.returns(["", "", stub(success?: true)]) + resource_deployer(kubectl_times: 0, prune_whitelist: [whitelist_kind]).deploy!([resource], false, true) + end + + def test_deploy_no_prune_doesnt_prune + whitelist_kind = "fake_kind" + resource = build_mock_resource + KubernetesDeploy::Kubectl.any_instance.expects(:run).with do |*args| + !args.include?("--prune-whitelist=#{whitelist_kind}") + end.returns(["", "", stub(success?: true)]) + resource_deployer(kubectl_times: 0, prune_whitelist: [whitelist_kind]).deploy!([resource], false, false) + end + + def test_deploy_verify_false_message + resource = build_mock_resource + resource_deployer.deploy!([resource], false, false) + logger.print_summary(:done) # Force logger to flush + assert_logs_match_all(["Deploy result verification is disabled for this deploy."]) + end + + def test_deploy_time_out_error + resource = build_mock_resource(final_status: "timeout") + watcher = mock("ResourceWatcher") + watcher.expects(:run).returns(true) + KubernetesDeploy::ResourceWatcher.expects(:new).returns(watcher) + assert_raises(KubernetesDeploy::DeploymentTimeoutError) do + resource_deployer.deploy!([resource], true, false) + end + end + + def test_deploy_verify_false_no_timeout + resource = build_mock_resource(final_status: "timeout") + resource_deployer.deploy!([resource], false, false) + logger.print_summary(:done) # Force logger to flush + assert_logs_match_all(["Deploy result verification is disabled for this deploy."]) + end + + def test_deploy_failure_error + resource = build_mock_resource(final_status: "failure") + watcher = mock("ResourceWatcher") + watcher.expects(:run).returns(true) + KubernetesDeploy::ResourceWatcher.expects(:new).returns(watcher) + assert_raises(KubernetesDeploy::FatalDeploymentError) do + resource_deployer.deploy!([resource], true, false) + end + end + + def test_deploy_verify_false_no_failure_error + resource = build_mock_resource(final_status: "failure") + resource_deployer.deploy!([resource], false, false) + logger.print_summary(:done) # Force logger to flush + assert_logs_match_all(["Deploy result verification is disabled for this deploy."]) + end + + def test_predeploy_priority_resources_respectes_pre_deploy_list + kind = "MockResource" + resource = build_mock_resource + watcher = mock("ResourceWatcher") + watcher.expects(:run).returns(true) + KubernetesDeploy::ResourceWatcher.expects(:new).returns(watcher) + priority_list = [kind] + resource_deployer.predeploy_priority_resources([resource], priority_list) + end + + def test_predeploy_priority_resources_respectes_empty_pre_deploy_list + resource = build_mock_resource + priority_list = [] + KubernetesDeploy::ResourceWatcher.expects(:new).times(0) + resource_deployer(kubectl_times: 0).predeploy_priority_resources([resource], priority_list) + end + + private + + def resource_deployer(kubectl_times: 2, prune_whitelist: []) + unless kubectl_times == 0 + KubernetesDeploy::Kubectl.expects(:new).returns(build_runless_kubectl).times(kubectl_times) + end + @deployer = KubernetesDeploy::ResourceDeployer.new(current_sha: 'test-sha', + statsd_tags: [], task_config: task_config, prune_whitelist: prune_whitelist, + max_watch_seconds: 60, selector: nil) + end + + def build_mock_resource(final_status: "success", hits_to_complete: 0, name: "web-pod") + MockResource.new(name, hits_to_complete, final_status) + end +end diff --git a/test/unit/kubernetes-deploy/resource_watcher_test.rb b/test/unit/kubernetes-deploy/resource_watcher_test.rb index 94f45410f..54223846b 100644 --- a/test/unit/kubernetes-deploy/resource_watcher_test.rb +++ b/test/unit/kubernetes-deploy/resource_watcher_test.rb @@ -123,58 +123,6 @@ def build_watcher(resources) ) end - MockResource = Struct.new(:id, :hits_to_complete, :status) do - def debug_message(*) - @debug_message - end - - def sync(_cache) - @hits ||= 0 - @hits += 1 - end - - def after_sync - end - - def type - "MockResource" - end - alias_method :kubectl_resource_type, :type - - def deploy_succeeded? - status == "success" && hits_complete? - end - - def deploy_failed? - status == "failed" && hits_complete? - end - - def deploy_timed_out? - status == "timeout" && hits_complete? - end - - def timeout - hits_to_complete - end - - def sync_debug_info(_) - @debug_message = "Something went wrong" - end - - def pretty_status - "#{id} #{status} (#{@hits} hits)" - end - - def report_status_to_statsd(watch_time) - end - - private - - def hits_complete? - @hits >= hits_to_complete - end - end - def build_mock_resource(final_status: "success", hits_to_complete: 1, name: "web-pod") MockResource.new(name, hits_to_complete, final_status) end diff --git a/test/unit/resource_cache_test.rb b/test/unit/resource_cache_test.rb index 7f4fe5f4c..8b0b0d11f 100644 --- a/test/unit/resource_cache_test.rb +++ b/test/unit/resource_cache_test.rb @@ -16,6 +16,14 @@ def test_get_instance_populates_the_cache_and_returns_instance_hash assert_equal(pods[1].kubectl_response, @cache.get_instance("FakePod", pods[1].name)) end + def test_get_instance_populates_the_cache_and_returns_instance_hash_global_kind + @task_config.global_kinds = %w(FakeNode).map(&:downcase) + nodes = build_fake_nodes(2) + stub_kind_get("FakeNode", items: nodes.map(&:kubectl_response), times: 1, use_namespace: false) + assert_equal(nodes[0].kubectl_response, @cache.get_instance("FakeNode", nodes[0].name)) + assert_equal(nodes[1].kubectl_response, @cache.get_instance("FakeNode", nodes[1].name)) + end + def test_get_instance_returns_empty_hash_if_pod_not_found pods = build_fake_pods(2) stub_kind_get("FakePod", items: pods.map(&:kubectl_response), times: 1) @@ -81,6 +89,10 @@ def test_concurrently_syncing_huge_numbers_of_resources_makes_exactly_one_kubect private + def build_fake_nodes(num) + num.times.map { |n| FakeNode.new("node#{n}") } + end + def build_fake_pods(num) num.times.map { |n| FakePod.new("pod#{n}") } end @@ -141,4 +153,9 @@ def sync(mediator) end class FakePod < MockResource; end class FakeConfigMap < MockResource; end + class FakeNode < MockResource + def global? + true + end + end end