K8s: Get trash fixes (#106411)

This commit is contained in:
Stephanie Hingtgen
2025-06-11 13:54:38 -05:00
committed by GitHub
parent 8fdf86e56f
commit 7864c1660f
12 changed files with 367 additions and 4 deletions

View File

@ -780,7 +780,14 @@ func (b *backend) getHistory(ctx context.Context, req *resourcepb.ListRequest, c
listReq.MinRV = latestDeletedRV + 1
}
rows, err := dbutil.QueryRows(ctx, tx, sqlResourceHistoryGet, listReq)
var rows db.Rows
if listReq.Trash {
// unlike history, trash will not return an object if an object of the same name is live
// (i.e. in the resource table)
rows, err = dbutil.QueryRows(ctx, tx, sqlResourceTrash, listReq)
} else {
rows, err = dbutil.QueryRows(ctx, tx, sqlResourceHistoryGet, listReq)
}
if rows != nil {
defer func() {
if err := rows.Close(); err != nil {

View File

@ -15,9 +15,6 @@ WHERE 1 = 1
{{ if .Key.Name }}
AND {{ .Ident "name" }} = {{ .Arg .Key.Name }}
{{ end }}
{{ if .Trash }}
AND {{ .Ident "action" }} = 3
{{ end }}
{{ if (gt .StartRV 0) }}
{{ if .SortAscending }}
AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }}

View File

@ -0,0 +1,54 @@
SELECT
h.{{ .Ident "guid" }},
h.{{ .Ident "resource_version" }},
h.{{ .Ident "namespace" }},
h.{{ .Ident "group" }},
h.{{ .Ident "resource" }},
h.{{ .Ident "name" }},
h.{{ .Ident "folder" }},
h.{{ .Ident "value" }}
FROM {{ .Ident "resource_history" }} h
INNER JOIN (
SELECT {{ .Ident "name" }}, MAX({{ .Ident "resource_version" }}) as max_rv
FROM {{ .Ident "resource_history" }}
WHERE 1 = 1
AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }}
AND {{ .Ident "group" }} = {{ .Arg .Key.Group }}
AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }}
{{ if .Key.Name }}
AND {{ .Ident "name" }} = {{ .Arg .Key.Name }}
{{ end }}
AND {{ .Ident "action" }} = 3
{{ if (gt .StartRV 0) }}
{{ if .SortAscending }}
AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }}
{{ else }}
AND {{ .Ident "resource_version" }} < {{ .Arg .StartRV }}
{{ end }}
{{ end }}
{{ if (gt .MinRV 0) }}
AND {{ .Ident "resource_version" }} >= {{ .Arg .MinRV }}
{{ end }}
{{ if (gt .ExactRV 0) }}
AND {{ .Ident "resource_version" }} = {{ .Arg .ExactRV }}
{{ end }}
GROUP BY {{ .Ident "name" }}
) max_versions ON h.{{ .Ident "name" }} = max_versions.{{ .Ident "name" }}
AND h.{{ .Ident "resource_version" }} = max_versions.max_rv
WHERE 1 = 1
AND h.{{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }}
AND h.{{ .Ident "group" }} = {{ .Arg .Key.Group }}
AND h.{{ .Ident "resource" }} = {{ .Arg .Key.Resource }}
AND h.{{ .Ident "action" }} = 3
AND NOT EXISTS (
SELECT 1 FROM {{ .Ident "resource" }} r
WHERE r.{{ .Ident "namespace" }} = h.{{ .Ident "namespace" }}
AND r.{{ .Ident "group" }} = h.{{ .Ident "group" }}
AND r.{{ .Ident "resource" }} = h.{{ .Ident "resource" }}
AND r.{{ .Ident "name" }} = h.{{ .Ident "name" }}
)
{{ if .SortAscending }}
ORDER BY h.{{ .Ident "resource_version" }} ASC
{{ else }}
ORDER BY h.{{ .Ident "resource_version" }} DESC
{{ end }}

View File

@ -46,6 +46,7 @@ var (
sqlResourceHistoryGet = mustTemplate("resource_history_get.sql")
sqlResourceHistoryDelete = mustTemplate("resource_history_delete.sql")
sqlResourceHistoryPrune = mustTemplate("resource_history_prune.sql")
sqlResourceTrash = mustTemplate("resource_trash.sql")
sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql")
// sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql")

View File

@ -245,6 +245,9 @@ func TestUnifiedStorageQueries(t *testing.T) {
},
},
},
},
sqlResourceTrash: {
{
Name: "read trash",
Data: &sqlGetHistoryRequest{

View File

@ -0,0 +1,35 @@
SELECT
h.`guid`,
h.`resource_version`,
h.`namespace`,
h.`group`,
h.`resource`,
h.`name`,
h.`folder`,
h.`value`
FROM `resource_history` h
INNER JOIN (
SELECT `name`, MAX(`resource_version`) as max_rv
FROM `resource_history`
WHERE 1 = 1
AND `namespace` = 'nn'
AND `group` = 'gg'
AND `resource` = 'rr'
AND `action` = 3
AND `resource_version` < 123456
GROUP BY `name`
) max_versions ON h.`name` = max_versions.`name`
AND h.`resource_version` = max_versions.max_rv
WHERE 1 = 1
AND h.`namespace` = 'nn'
AND h.`group` = 'gg'
AND h.`resource` = 'rr'
AND h.`action` = 3
AND NOT EXISTS (
SELECT 1 FROM `resource` r
WHERE r.`namespace` = h.`namespace`
AND r.`group` = h.`group`
AND r.`resource` = h.`resource`
AND r.`name` = h.`name`
)
ORDER BY h.`resource_version` DESC

View File

@ -0,0 +1,34 @@
SELECT
h.`guid`,
h.`resource_version`,
h.`namespace`,
h.`group`,
h.`resource`,
h.`name`,
h.`folder`,
h.`value`
FROM `resource_history` h
INNER JOIN (
SELECT `name`, MAX(`resource_version`) as max_rv
FROM `resource_history`
WHERE 1 = 1
AND `namespace` = 'nn'
AND `group` = 'gg'
AND `resource` = 'rr'
AND `action` = 3
GROUP BY `name`
) max_versions ON h.`name` = max_versions.`name`
AND h.`resource_version` = max_versions.max_rv
WHERE 1 = 1
AND h.`namespace` = 'nn'
AND h.`group` = 'gg'
AND h.`resource` = 'rr'
AND h.`action` = 3
AND NOT EXISTS (
SELECT 1 FROM `resource` r
WHERE r.`namespace` = h.`namespace`
AND r.`group` = h.`group`
AND r.`resource` = h.`resource`
AND r.`name` = h.`name`
)
ORDER BY h.`resource_version` DESC

View File

@ -0,0 +1,35 @@
SELECT
h."guid",
h."resource_version",
h."namespace",
h."group",
h."resource",
h."name",
h."folder",
h."value"
FROM "resource_history" h
INNER JOIN (
SELECT "name", MAX("resource_version") as max_rv
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "action" = 3
AND "resource_version" < 123456
GROUP BY "name"
) max_versions ON h."name" = max_versions."name"
AND h."resource_version" = max_versions.max_rv
WHERE 1 = 1
AND h."namespace" = 'nn'
AND h."group" = 'gg'
AND h."resource" = 'rr'
AND h."action" = 3
AND NOT EXISTS (
SELECT 1 FROM "resource" r
WHERE r."namespace" = h."namespace"
AND r."group" = h."group"
AND r."resource" = h."resource"
AND r."name" = h."name"
)
ORDER BY h."resource_version" DESC

View File

@ -0,0 +1,34 @@
SELECT
h."guid",
h."resource_version",
h."namespace",
h."group",
h."resource",
h."name",
h."folder",
h."value"
FROM "resource_history" h
INNER JOIN (
SELECT "name", MAX("resource_version") as max_rv
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "action" = 3
GROUP BY "name"
) max_versions ON h."name" = max_versions."name"
AND h."resource_version" = max_versions.max_rv
WHERE 1 = 1
AND h."namespace" = 'nn'
AND h."group" = 'gg'
AND h."resource" = 'rr'
AND h."action" = 3
AND NOT EXISTS (
SELECT 1 FROM "resource" r
WHERE r."namespace" = h."namespace"
AND r."group" = h."group"
AND r."resource" = h."resource"
AND r."name" = h."name"
)
ORDER BY h."resource_version" DESC

View File

@ -0,0 +1,35 @@
SELECT
h."guid",
h."resource_version",
h."namespace",
h."group",
h."resource",
h."name",
h."folder",
h."value"
FROM "resource_history" h
INNER JOIN (
SELECT "name", MAX("resource_version") as max_rv
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "action" = 3
AND "resource_version" < 123456
GROUP BY "name"
) max_versions ON h."name" = max_versions."name"
AND h."resource_version" = max_versions.max_rv
WHERE 1 = 1
AND h."namespace" = 'nn'
AND h."group" = 'gg'
AND h."resource" = 'rr'
AND h."action" = 3
AND NOT EXISTS (
SELECT 1 FROM "resource" r
WHERE r."namespace" = h."namespace"
AND r."group" = h."group"
AND r."resource" = h."resource"
AND r."name" = h."name"
)
ORDER BY h."resource_version" DESC

View File

@ -0,0 +1,34 @@
SELECT
h."guid",
h."resource_version",
h."namespace",
h."group",
h."resource",
h."name",
h."folder",
h."value"
FROM "resource_history" h
INNER JOIN (
SELECT "name", MAX("resource_version") as max_rv
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "action" = 3
GROUP BY "name"
) max_versions ON h."name" = max_versions."name"
AND h."resource_version" = max_versions.max_rv
WHERE 1 = 1
AND h."namespace" = 'nn'
AND h."group" = 'gg'
AND h."resource" = 'rr'
AND h."action" = 3
AND NOT EXISTS (
SELECT 1 FROM "resource" r
WHERE r."namespace" = h."namespace"
AND r."group" = h."group"
AND r."resource" = h."resource"
AND r."name" = h."name"
)
ORDER BY h."resource_version" DESC

View File

@ -35,6 +35,7 @@ const (
TestGetResourceStats = "get resource stats"
TestListHistory = "list history"
TestListHistoryErrorReporting = "list history error reporting"
TestListTrash = "list trash"
TestCreateNewResource = "create new resource"
)
@ -79,6 +80,7 @@ func RunStorageBackendTest(t *testing.T, newBackend NewBackendFunc, opts *TestOp
{TestGetResourceStats, runTestIntegrationBackendGetResourceStats},
{TestListHistory, runTestIntegrationBackendListHistory},
{TestListHistoryErrorReporting, runTestIntegrationBackendListHistoryErrorReporting},
{TestListTrash, runTestIntegrationBackendTrash},
{TestCreateNewResource, runTestIntegrationBackendCreateNewResource},
}
@ -1128,3 +1130,95 @@ func newServer(t *testing.T, b resource.StorageBackend) resource.ResourceServer
return server
}
func runTestIntegrationBackendTrash(t *testing.T, backend resource.StorageBackend, nsPrefix string) {
ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second))
server := newServer(t, backend)
ns := nsPrefix + "-ns-trash"
// item1 deleted with multiple history events
rv1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv1, int64(0))
rvDelete1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvDelete1, rv1)
rvDelete2, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvDelete2, rvDelete1)
// item2 deleted and recreated, should not be returned in trash
rv2, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv2, int64(0))
rvDelete3, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_DELETED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rvDelete3, rv2)
rv3, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns))
require.NoError(t, err)
require.Greater(t, rv3, int64(0))
tests := []struct {
name string
request *resourcepb.ListRequest
expectedVersions []int64
expectedValues []string
minExpectedHeadRV int64
expectedContinueRV int64
expectedSortAsc bool
}{
{
name: "returns the latest delete event",
request: &resourcepb.ListRequest{
Source: resourcepb.ListRequest_TRASH,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
Name: "item1",
},
},
},
expectedVersions: []int64{rvDelete2},
expectedValues: []string{"item1 DELETED"},
minExpectedHeadRV: rvDelete2,
expectedContinueRV: rvDelete2,
expectedSortAsc: false,
},
{
name: "does not return a version in the resource table",
request: &resourcepb.ListRequest{
Source: resourcepb.ListRequest_TRASH,
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: ns,
Group: "group",
Resource: "resource",
Name: "item2",
},
},
},
expectedVersions: []int64{},
expectedValues: []string{},
minExpectedHeadRV: rv3,
expectedContinueRV: rv3,
expectedSortAsc: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
res, err := server.List(ctx, tc.request)
require.NoError(t, err)
require.Nil(t, res.Error)
expectedItemCount := len(tc.expectedVersions)
require.Len(t, res.Items, expectedItemCount)
for i := 0; i < expectedItemCount; i++ {
require.Equal(t, tc.expectedVersions[i], res.Items[i].ResourceVersion)
require.Contains(t, string(res.Items[i].Value), tc.expectedValues[i])
}
require.GreaterOrEqual(t, res.ResourceVersion, tc.minExpectedHeadRV)
})
}
}