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

New DeploymentStrategy type for JaegerSpec.Strategy #704

Merged
merged 7 commits into from
Oct 22, 2019

Conversation

volmedo
Copy link
Contributor

@volmedo volmedo commented Oct 17, 2019

This PR adds a new type, DeploymentStrategy, to be used in JaegerSpec.Strategy instead of just a string. Constants based on the new type are also defined to define valid values.

When merged, this PR will close #137.

To do

  • make sure all unserialized values are converted to lower case
  • a test ensuring that current CRs with a mixed-case strategy are correctly parsed
  • the constant in the strategy package, mentioned in Make JaegerSpec.Strategy a type #137

@volmedo volmedo force-pushed the spec-strategy-type branch 2 times, most recently from ea92767 to 3949e62 Compare October 17, 2019 12:18
@volmedo volmedo force-pushed the spec-strategy-type branch from 3949e62 to cafa6ce Compare October 17, 2019 12:21
Copy link
Contributor

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

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

Looks really good so far! It's missing a couple of things, like:

  1. make sure all unserialized values are converted to lower case
  2. possibly a test ensuring that current CRs with a mixed-case strategy are correctly parsed
  3. the constant in the strategy package, mentioned in Make JaegerSpec.Strategy a type #137

Apart from that, LGTM.

// to Kafka, and the storage options will be used in the Ingester instead
if strings.EqualFold(c.jaeger.Spec.Strategy, "streaming") {
if c.jaeger.Spec.Strategy == v1.DeploymentStrategyStreaming {
Copy link
Contributor

Choose a reason for hiding this comment

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

What would happen if we have a CR with a strategy set to Streaming?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was assuming that the value was being normalized somewhere else. Sorry, my bad.

@@ -49,11 +49,33 @@ const (
IngressSecurityOAuthProxy IngressSecurityType = "oauth-proxy"
)

// DeploymentStrategy represents the possible values for deployment strategies
// +k8s:openapi-gen=true
type DeploymentStrategy string
Copy link
Contributor

Choose a reason for hiding this comment

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

You could implement the UnmarshalJSON method to the type, to ensure the values read from JSON are converted to lowercase.

@volmedo volmedo changed the title New DeploymentStrategy type for JaegerSpec.Strategy [WIP] New DeploymentStrategy type for JaegerSpec.Strategy Oct 18, 2019
@volmedo
Copy link
Contributor Author

volmedo commented Oct 18, 2019

  1. make sure all unserialized values are converted to lower case

Where is the unmarshalling being done? I suppose it is done by some library from k8s or the operator framework. If that is the case, is it enough to make the new type implement the Unmarshaler interface?

  1. possibly a test ensuring that current CRs with a mixed-case strategy are correctly parsed

Where should this test live? Are there any other similar tests?

@jpkrohling
Copy link
Contributor

Where is the unmarshalling being done?

It's done by the Operator SDK. It's sufficient to have a couple of methods, like these from the Options object:

func (o *Options) UnmarshalJSON(b []byte) error {
var entries map[string]interface{}
d := json.NewDecoder(bytes.NewReader(b))
d.UseNumber()
if err := d.Decode(&entries); err != nil {
return err
}
o.parse(entries)
o.json = b
return nil
}
// MarshalJSON specifies how to convert this object into JSON
func (o Options) MarshalJSON() ([]byte, error) {
if len(o.json) == 0 && len(o.opts) == 0 {
return []byte("{}"), nil
} else if len(o.json) == 0 && len(o.opts) > 0 {
return json.Marshal(o.opts)
}
return o.json, nil
}

I think you only need to implement the UnmarshalJSON, but needs confirmation.

We have some tests for the same object that could be used as reference:

func TestSimpleOption(t *testing.T) {
o := Options{}
o.UnmarshalJSON([]byte(`{"key": "value"}`))
args := o.ToArgs()
assert.Equal(t, "--key=value", args[0])
}

Copy link
Contributor

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

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

LGTM. There's only one minor comment about the import renaming for corev1 vs. jv1. If you don't want to change it in this PR, please create an issue so that we don't lose track of it.

@@ -9,12 +9,13 @@ import (
"k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1"

jv1 "github.com/jaegertracing/jaeger-operator/pkg/apis/jaegertracing/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 typically use v1 for Jaeger, and corev1 for k8s.io/api/core/v1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes a lot of sense!

I saw that v1 was already in use and invented a different alias for Jaeger. Didn't think about the possibility of renaming the existing one :). Will do it now.

Copy link
Contributor

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

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

Sorry, let me take back my approval :-) Just realized that it's still missing the serialization tests, to ensure backwards compatibility (which is probably why this is still WIP).

@volmedo
Copy link
Contributor Author

volmedo commented Oct 21, 2019

Sorry, let me take back my approval :-) Just realized that it's still missing the serialization tests, to ensure backwards compatibility (which is probably why this is still WIP).

Yes, it's WIP because of that. I added three items to a todo list for the PR and I began with the last one instead of the first one :).

@volmedo volmedo force-pushed the spec-strategy-type branch from 0da9d6d to ae31e2b Compare October 21, 2019 21:37
@volmedo
Copy link
Contributor Author

volmedo commented Oct 21, 2019

I added the unmarshalling code and the corresponding tests.

I implemented encoding.UnmarshalText for the type instead of json.UnmarshalJSON since we are dealing with a single string value. The docs say that json.Unmarshal uses encoding.UnmarshalText if json.UnmarshalJSON is not available.

Aside from the tests, I also did some manual tests applying several modified yaml files and everything worked as expected. BTW, is there any end to end test that involves applying malformed deployment yaml files?

I didn't include code for marshalling. Since the underlying type is string, marshalling comes for free. I wrote tests to check this and to add protection for custom marshalling code that could be added in the future.

I've also removed the WIP label from the PR as it should be ready now :)

@volmedo volmedo changed the title [WIP] New DeploymentStrategy type for JaegerSpec.Strategy New DeploymentStrategy type for JaegerSpec.Strategy Oct 21, 2019
@volmedo volmedo requested a review from jpkrohling October 21, 2019 21:42
Copy link
Contributor

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

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

LGTM. Once the nil pointer check is clarified, this can be merged!

// UnmarshalText implements encoding.TextUnmarshaler to ensure that JSON values in the
// strategy field of JSON jaeger specs are interpreted in a case-insensitive manner
func (ds *DeploymentStrategy) UnmarshalText(text []byte) error {
if ds == nil {
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 interesting. I'm probably missing something obvious, but how would this be triggered? Wouldn't a nil object fail with a nil reference before reaching this code? Can you come up with a test case for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not obvious. It is possible for the function to be called on a nil receiver when interfaces are involved (as is the case here).

I was digging into the docs and I had a look at how json.RawMessage.UnmarshalJSON is implemented. The check for nil felt strange, so I did a bit of research and came up with this https://tour.golang.org/methods/12

Wonderful Go! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, I guess this kind of check should be implemented in other types implementing interfaces for additional safety. Options come to my mind, implementing both json.Marshaler and json.Unmarshaler interfaces, but I'm sure there are several others.

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL! I'm merging this one. Would you mind creating an issue to do the same nil check in the other relevant types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do!

@jpkrohling jpkrohling merged commit 281ce62 into jaegertracing:master Oct 22, 2019
@jpkrohling
Copy link
Contributor

Thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make JaegerSpec.Strategy a type
2 participants