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

Test and remove panic possibilities in newDashboardCompatibleRelease #1445

Merged
merged 1 commit into from
Jan 14, 2020
Merged
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
12 changes: 11 additions & 1 deletion cmd/kubeops/internal/handler/dashboardcompat.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@ import (
h2 "k8s.io/helm/pkg/proto/hapi/release"
)

var (
// ErrUnableToConvertWithoutInfo indicates that the input release had nil Info or Chart.
ErrUnableToConvertWithoutInfo = fmt.Errorf("unable to convert release without info")
// ErrUnableToParseDeletionTime indicates that the deletion time of the h3 chart could not be parsed.
ErrFailedToParseDeletionTime = fmt.Errorf("failed to parse deletion time")
)

func newDashboardCompatibleRelease(h3r h3.Release) (h2.Release, error) {
if h3r.Info == nil || h3r.Chart == nil || h3r.Chart.Metadata == nil {
return h2.Release{}, ErrUnableToConvertWithoutInfo
}
var deleted *timestamp.Timestamp
if !h3r.Info.Deleted.IsZero() {
var err error
deleted, err = ptypes.TimestampProto(h3r.Info.Deleted.Time)
if err != nil {
return h2.Release{}, fmt.Errorf("Failed to parse deletion time %v", err)
return h2.Release{}, ErrFailedToParseDeletionTime
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's important to have the error text somewhere. That way, if a user reports that it's hitting this error we'd get more info (for example if .IsZero() is not working as expected or if the Time is no longer valid).

If the only reason to set a fixed message is the test, you can still do string.Contains(got, want) instead of got != want which is a bit more flexible and unlikely to report a false-positive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See #1451 Thanks!

}
}
return h2.Release{
Expand Down
103 changes: 94 additions & 9 deletions cmd/kubeops/internal/handler/dashboardcompat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ package handler

import (
"net/http/httptest"
"runtime/debug"

"github.com/golang/protobuf/ptypes/timestamp"
"github.com/google/go-cmp/cmp"
"github.com/kubeapps/common/response"
h3chart "helm.sh/helm/v3/pkg/chart"
h3 "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
h2chart "k8s.io/helm/pkg/proto/hapi/chart"
h2 "k8s.io/helm/pkg/proto/hapi/release"

"testing"
)

func TestNewDashboardCompatibleRelease(t *testing.T) {
const (
validSeconds = 1452902400
invalidSeconds = 253402300801
)
var (
validDeletedTime = helmtime.Unix(validSeconds, 0)
invalidDeletedTime = helmtime.Unix(invalidSeconds, 0)
)

type testScenario struct {
// Scenario params
Description string
Expand All @@ -25,6 +35,7 @@ func TestNewDashboardCompatibleRelease(t *testing.T) {
// and how to format the fields of a Helm 2 release.
// E.g. spew.Dumps is a valid marhsalling function.
MarshallingFunction func(h2.Release) string
ExpectedError error
}
tests := []testScenario{
{
Expand Down Expand Up @@ -113,20 +124,93 @@ func TestNewDashboardCompatibleRelease(t *testing.T) {
},
},
},
{
Description: "returns an error rather than panicking for a Helm 3 Release without Info",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Incomplete",
Chart: &h3chart.Chart{},
},
ExpectedError: ErrUnableToConvertWithoutInfo,
},
{
Description: "returns an error for a Helm 3 Release without Chart",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Incomplete",
Info: &h3.Info{},
},
ExpectedError: ErrUnableToConvertWithoutInfo,
},
{
Description: "returns an error for a Helm 3 Release without Chart.Metadata",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Incomplete",
Info: &h3.Info{},
Chart: &h3chart.Chart{},
},
ExpectedError: ErrUnableToConvertWithoutInfo,
},
{
Description: "parses and includes the deleted time",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Foo",
Info: &h3.Info{
Status: h3.StatusDeployed,
Deleted: validDeletedTime,
},
Chart: &h3chart.Chart{
Metadata: &h3chart.Metadata{},
Values: map[string]interface{}{},
},
},
Helm2Release: h2.Release{
Name: "Foo",
Info: &h2.Info{
Status: &h2.Status{
Code: h2.Status_DEPLOYED,
},
Deleted: &timestamp.Timestamp{Seconds: validSeconds},
},
Chart: &h2chart.Chart{
Metadata: &h2chart.Metadata{},
Values: &h2chart.Config{
Raw: "{}\n",
},
},
Config: &h2chart.Config{
Raw: "{}\n",
},
},
},
{
Description: "returns an error if the deleted time cannot be parsed",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Foo",
Info: &h3.Info{
Status: h3.StatusDeployed,
Deleted: invalidDeletedTime,
},
Chart: &h3chart.Chart{
Metadata: &h3chart.Metadata{},
},
},
ExpectedError: ErrFailedToParseDeletionTime,
},
}

for _, test := range tests {
t.Run(test.Description, func(t *testing.T) {
// Capture the panic and report it in an orderly fashion
defer func() {
if r := recover(); r != nil {
t.Errorf("Got a panic: %v. \nStacktrace: \n%s", r, string(debug.Stack()))
}
}()
// Perform conversion
compatibleH3rls, err := newDashboardCompatibleRelease(test.Helm3Release)
if got, want := err, test.ExpectedError; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
return
}
// Marshall both: Compatible H3Release and H2Release
h3Marshalled := test.MarshallingFunction(compatibleH3rls)
Expand All @@ -135,7 +219,8 @@ func TestNewDashboardCompatibleRelease(t *testing.T) {
t.Logf("Marshalled Helm 2 Release %s", h2Marshalled)
// Check result
if h3Marshalled != h2Marshalled {
t.Errorf("Not equal: %s", cmp.Diff(h3Marshalled, h2Marshalled))
t.Errorf("Not equal:\nMarshalled diff: %s\nUnMarshalled diff: %s",
cmp.Diff(h3Marshalled, h2Marshalled), cmp.Diff(compatibleH3rls, test.Helm2Release))
}
})
}
Expand Down
9 changes: 9 additions & 0 deletions cmd/kubeops/internal/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"sort"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -312,6 +313,14 @@ func TestActions(t *testing.T) {
if err != nil {
t.Fatalf("%+v", err)
}
// The Helm memory driver does not appear to have consistent ordering.
// See https://github.com/helm/helm/issues/7263
// Just sort by "name.version.namespace" which is good enough here.
sort.Slice(releases, func(i, j int) bool {
iKey := fmt.Sprintf("%s.%d.%s", releases[i].Name, releases[i].Version, releases[i].Namespace)
jKey := fmt.Sprintf("%s.%d.%s", releases[j].Name, releases[j].Version, releases[j].Namespace)
return iKey < jKey
})
if !cmp.Equal(releases, test.RemainingReleases, releaseComparer) {
t.Errorf("Unexpected remaining releases. Diff:\n%s", cmp.Diff(releases, test.RemainingReleases, releaseComparer))
}
Expand Down