Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Fix] Grouping Cluster Resources by group and Kind #875

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions lib/krane/deploy_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def predeploy_sequence
Pod
).map { |r| [r, default_group] }

crs = cluster_resource_discoverer.crds.select(&:predeployed?).map { |cr| [cr.kind, { group: cr.group }] }
crs = cluster_resource_discoverer.crds.select(&:predeployed?).map { |cr| [cr.group_kind, { group: cr.group }] }
Hash[before_crs + crs + after_crs]
end

Expand Down Expand Up @@ -238,9 +238,10 @@ def secrets_from_ejson
def discover_resources
@logger.info("Discovering resources:")
resources = []
crds_by_kind = cluster_resource_discoverer.crds.group_by(&:kind)
crds_by_group_kind = cluster_resource_discoverer.crds.group_by(&:group_kind)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there tests to cover this change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be great to test this change, this is supposed to change the order of the items

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added new tests to cover these changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is supposed to change the order of the items

I don't quite understand what you mean by this, could you explain please?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, sorry, I misread the issue in #873 and I thought a consequence of this PR is that it'll change the order of the items in some list, however, this change is about grouping differently. Sorry for the noise

@template_sets.with_resource_definitions(current_sha: @current_sha, bindings: @bindings) do |r_def|
crd = crds_by_kind[r_def["kind"]]&.first
group, kind = group_kind_for_r_def(r_def)
crd = crds_by_group_kind["#{kind}.#{group}"]&.first
r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def,
statsd_tags: @namespace_tags, crd: crd, global_names: @task_config.global_kinds)
resources << r
Expand Down Expand Up @@ -389,5 +390,12 @@ def with_retries(limit)
retried += 1
end
end

def group_kind_for_r_def(r_def)
grouping, version = r_def.dig("apiVersion").split("/")
group = version ? grouping : "core"
kind = r_def["kind"].to_s
[group, kind]
end
end
end
8 changes: 6 additions & 2 deletions lib/krane/kubernetes_resource/custom_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def status
end

def type
kind
group_kind
Copy link
Contributor

@timothysmith0609 timothysmith0609 Mar 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For other reviewers:

#type is already a somewhat overloaded method. In particular, KubernetesResource#type has as body:

    def type
      @type || self.class.kind
    end

#type is mainly used internally, most critically in DeployTask#deploy_has_priority_resources?, though it's value also appears in the tool's output.

#type is also aliased to KubernetesResource#kubectl_resource_type, which is used by ResourceCache. There is already a case where #type is overridden to include the group suffix.

All this to say, I'm hoping the use of group_kind here isn't too controversial :)

end

def validate_definition(*, **)
Expand All @@ -77,7 +77,11 @@ def validate_definition(*, **)
private

def kind
@definition["kind"]
@crd.kind
end

def group_kind
@crd.group_kind
end

def rollout_conditions
Expand Down
4 changes: 4 additions & 0 deletions lib/krane/kubernetes_resource/custom_resource_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def group
@definition.dig("spec", "group")
end

def group_kind
"#{kind}.#{group}"
end

Comment on lines +55 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour of CustomResourceDefinition#kind and #group is a little strange (you might expect it to return CRD and apiextensions, respectively), but since this is previous behaviour I think it's ok to leverage it

def prunable?
prunable = krane_annotation_value("prunable")
prunable == "true"
Expand Down
71 changes: 71 additions & 0 deletions test/fixtures/crd/for_group_kind_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: same-kinds.with-rollout-conditions.example.io
labels:
app: krane
annotations:
krane.shopify.io/instance-rollout-conditions: "true"
spec:
group: with-rollout-conditions.example.io
names:
kind: SameKind
listKind: SameKindList
plural: same-kinds
singular: same-kind
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
status:
type: object
properties:
observedGeneration:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
reason:
type: string
message:
type: string
status:
type: string

---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: same-kinds.no-rollout-conditions.example.io
labels:
app: krane
spec:
group: no-rollout-conditions.example.io
names:
kind: SameKind
listKind: SameKindList
plural: same-kinds
singular: same-kind
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
10 changes: 10 additions & 0 deletions test/fixtures/crd/for_group_kind_test_cr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
apiVersion: "with-rollout-conditions.example.io/v1"
kind: SameKind
metadata:
name: monitored
---
apiVersion: "no-rollout-conditions.example.io/v1"
kind: SameKind
metadata:
name: unmonitored
12 changes: 8 additions & 4 deletions test/helpers/fixture_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def app_name
def refute_resource_exists(type, name)
client = if %w(daemonset deployment replicaset statefulset).include?(type)
apps_v1_kubeclient
elsif %w(ingress networkpolicy).include?(type)
networking_v1_kubeclient
elsif %w(ingress networkpolicy).include?(type)
networking_v1_kubeclient
else
kubeclient
end
Expand Down Expand Up @@ -96,12 +96,16 @@ def assert_pvc_status(pvc_name, status)
end

def assert_ingress_up(ing_name)
ing = networking_v1_kubeclient.get_ingresses(namespace: namespace, label_selector: "name=#{ing_name},app=#{app_name}")
ing = networking_v1_kubeclient.get_ingresses(
namespace: namespace, label_selector: "name=#{ing_name},app=#{app_name}"
)
assert_equal(1, ing.size, "Expected 1 #{ing_name} ingress, got #{ing.size}")
end

def assert_configmap_present(cm_name, expected_data)
configmaps = kubeclient.get_config_maps(namespace: namespace, label_selector: "name=#{cm_name},app=#{app_name}")
configmaps = kubeclient.get_config_maps(
namespace: namespace, label_selector: "name=#{cm_name},app=#{app_name}"
)
assert_equal(1, configmaps.size, "Expected 1 configmap, got #{configmaps.size}")
assert_equal(expected_data, configmaps.first["data"].to_h)
end
Expand Down
57 changes: 47 additions & 10 deletions test/integration-serial/serial_deploy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ def test_custom_resources_predeployed
end
assert_deploy_success(result)

mail_cr_id = "#{add_unique_prefix_for_test('Mail')}/my-first-mail"
thing_cr_id = "#{add_unique_prefix_for_test('Thing')}/my-first-thing"
widget_cr_id = "#{add_unique_prefix_for_test('Widget')}/my-first-widget"
mail_cr_id = "#{add_unique_prefix_for_test('Mail')}.stable.example.io/my-first-mail"
thing_cr_id = "#{add_unique_prefix_for_test('Thing')}.stable.example.io/my-first-thing"
widget_cr_id = "#{add_unique_prefix_for_test('Widget')}.stable.example.io/my-first-widget"
assert_logs_match_all([
/Phase 3: Predeploying priority resources/,
/Successfully deployed in \d.\ds: #{mail_cr_id}/,
Expand All @@ -291,11 +291,11 @@ def test_cr_deploys_without_rollout_conditions_when_none_present
end

assert_deploy_success(result)
prefixed_kind = add_unique_prefix_for_test("Widget")
prefixed_kind = "#{add_unique_prefix_for_test('Widget')}.stable.example.io"
assert_logs_match_all([
"Don't know how to monitor resources of type #{prefixed_kind}.",
"Assuming #{prefixed_kind}/my-first-widget deployed successfully.",
%r{Widget/my-first-widget\s+Exists},
%r{#{prefixed_kind}/my-first-widget\s+Exists},
])
end

Expand All @@ -320,10 +320,11 @@ def test_cr_success_with_default_rollout_conditions
cr.merge!(success_conditions)
cr["kind"] = add_unique_prefix_for_test(cr["kind"])
end
prefixed_name = "#{add_unique_prefix_for_test('Parameterized')}.stable.example.io"
assert_deploy_success(result)
assert_logs_match_all([
%r{Successfully deployed in .*: #{add_unique_prefix_for_test("Parameterized")}\/with-default-params},
%r{Parameterized/with-default-params\s+Healthy},
%r{Successfully deployed in .*: #{prefixed_name}\/with-default-params},
%r{Parameterized.stable.example.io/with-default-params\s+Healthy},
])
end

Expand Down Expand Up @@ -374,12 +375,47 @@ def test_cr_failure_with_default_rollout_conditions
assert_deploy_failure(result)

assert_logs_match_all([
"Parameterized/with-default-params: FAILED",
"Parameterized.stable.example.io/with-default-params: FAILED",
"custom resource rollout failed",
"Final status: Unhealthy",
], in_order: true)
end

# # Make 2 CRDs of same kind, different group. We expect the appropriate one to be monitored and
# # the other to be unmonitored for rollout conditions
def test_cr_references_parent_crd_by_group_kind
assert_deploy_success(deploy_global_fixtures("crd", subset: %(for_group_kind_test.yml)))
success_conditions = {
"status" => {
"observedGeneration" => 1,
"conditions" => [
{
"type" => "Ready",
"reason" => "test",
"message" => "test",
"status" => "True",
},
],
},
}

result = deploy_fixtures("crd", subset: %(for_group_kind_test_cr.yml)) do |resource|
monitored = resource["for_group_kind_test_cr.yml"]["SameKind"][0]
monitored["kind"] = add_unique_prefix_for_test(monitored["kind"])
monitored.merge!(success_conditions)

unmonitored = resource["for_group_kind_test_cr.yml"]["SameKind"][1]
unmonitored["kind"] = add_unique_prefix_for_test(unmonitored["kind"])
end
assert_deploy_success(result)
prefixed_kind = add_unique_prefix_for_test("SameKind")
assert_logs_match_all([
/Successfully deployed in .*: /,
%r{#{prefixed_kind}.no-rollout-conditions.example.io/unmonitored Exists},
%r{#{prefixed_kind}.with-rollout-conditions.example.io/monitored Healthy},
])
end

def test_cr_success_with_arbitrary_rollout_conditions
assert_deploy_success(deploy_global_fixtures("crd", subset: %(with_custom_conditions.yml)))

Expand All @@ -398,8 +434,9 @@ def test_cr_success_with_arbitrary_rollout_conditions
cr.merge!(success_conditions)
end
assert_deploy_success(result)
prefixed_name = "#{add_unique_prefix_for_test('Customized')}.stable.example.io"
assert_logs_match_all([
%r{Successfully deployed in .*: #{add_unique_prefix_for_test("Customized")}\/with-customized-params},
%r{Successfully deployed in .*: #{prefixed_name}\/with-customized-params},
])
end

Expand Down Expand Up @@ -450,7 +487,7 @@ def test_deploying_crs_with_invalid_crd_conditions_fails
end
end
assert_deploy_failure(result)
prefixed_name = add_unique_prefix_for_test("Customized-with-customized-params")
prefixed_name = add_unique_prefix_for_test('Customized.stable.example.io-with-customized-params').to_s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most visible consequence of our change, and I would argue it's a feature: CRDs are now logged showing both their group and kind. This should make things easier to understand in the case of multiple kinds among difference groups

assert_logs_match_all([
/Invalid template: #{prefixed_name}/,
/Rollout conditions are not valid JSON/,
Expand Down
22 changes: 12 additions & 10 deletions test/integration/krane_deploy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,22 @@ def test_deploy_fails_with_empty_yaml
assert_deploy_failure(deploy_raw_fixtures("empty-resources", subset: %w[empty1.yml empty2.yml]))

assert_logs_match_all([
"All required parameters and files are present",
"Result: FAILURE",
"No deployable resources were found!",
], in_order: true)
"All required parameters and files are present",
"Result: FAILURE",
"No deployable resources were found!",
], in_order: true)
end

def test_deploy_fails_with_empty_erb
assert_deploy_failure(deploy_raw_fixtures("empty-resources", subset: %w[empty3.yml.erb empty4.yml.erb], render_erb: true))
assert_deploy_failure(
deploy_raw_fixtures("empty-resources", subset: %w[empty3.yml.erb empty4.yml.erb], render_erb: true)
)

assert_logs_match_all([
"All required parameters and files are present",
"Result: FAILURE",
"No deployable resources were found!",
], in_order: true)
"All required parameters and files are present",
"Result: FAILURE",
"No deployable resources were found!",
], in_order: true)
end

def test_service_account_predeployed_before_unmanaged_pod
Expand Down Expand Up @@ -135,7 +137,7 @@ def test_pruning_works
prune_matcher("role", "rbac.authorization.k8s.io", "role"),
prune_matcher("rolebinding", "rbac.authorization.k8s.io", "role-binding"),
prune_matcher("persistentvolumeclaim", "", "hello-pv-claim"),
prune_matcher("ingress", %w(networking.k8s.io extensions), "web")
prune_matcher("ingress", %w(networking.k8s.io extensions), "web"),
] # not necessarily listed in this order
expected_msgs = [/Pruned 2[013] resources and successfully deployed 6 resources/]
expected_pruned.map do |resource|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def test_cr_instance_fails_validation_when_rollout_conditions_for_crd_invalid
logger: @logger, statsd_tags: @statsd_tags, crd: crd,
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
"metadata" => { "name" => "test" },
})
cr.validate_definition(kubectl: kubectl)
Expand Down Expand Up @@ -129,6 +130,7 @@ def test_cr_instance_valid_when_rollout_conditions_for_crd_valid
logger: @logger, statsd_tags: [], crd: crd,
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
"metadata" => { "name" => "test" },
})
cr.validate_definition(kubectl: kubectl)
Expand All @@ -154,7 +156,11 @@ def test_instance_timeout_annotation
))
cr = Krane::KubernetesResource.build(namespace: "test", context: "test",
logger: @logger, statsd_tags: [], crd: crd,
definition: { "kind" => "UnitTest", "metadata" => { "name" => "test" } })
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add apiVersion to make these tests pass (since we now parse group info). Since all k8s objects require this field, it makes sense to add it instead of accommodating its absence

"metadata" => { "name" => "test" },
})
assert_equal(cr.timeout, 60)
end

Expand All @@ -171,6 +177,7 @@ def test_instance_timeout_messages_with_rollout_conditions
logger: @logger, statsd_tags: [], crd: crd,
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
"metadata" => {
"name" => "test",
},
Expand All @@ -195,6 +202,7 @@ def test_instance_timeout_messages_without_rollout_conditions
logger: @logger, statsd_tags: [], crd: crd,
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
"metadata" => {
"name" => "test",
},
Expand Down
3 changes: 2 additions & 1 deletion test/unit/resource_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ def test_warming_the_resource_cache_works_for_custom_resources
logger: @logger, statsd_tags: [], crd: crd,
definition: {
"kind" => "UnitTest",
"apiVersion" => "stable.example.io/v1",
"metadata" => {
"name" => "test",
},
})

stub_kind_get("UnitTest", times: 1)
stub_kind_get("UnitTest.stable.example.io", times: 1)
stub_kind_get("CustomResourceDefinition", times: 1)
resources = [cr, crd]
@cache.prewarm(resources)
Expand Down