Skip to content
This repository has been archived by the owner on Nov 15, 2022. It is now read-only.

introduce portMappings shortcut #242

Merged
merged 4 commits into from
Sep 14, 2017

Conversation

concaf
Copy link
Collaborator

@concaf concaf commented Aug 31, 2017

  • add docs

This commit adds a shortcut to ServiceSpec called portMappings.
This lets us set the port, targetPort and the protocol for a
service in a single line. This is parsed and converted to a
Kubernetes ServicePort object.

portMappings is an array of port:targetPort/protocol
definitions, so the syntax looks like -

portMappings:

  • port:targetPort/protocol
  • port:targetPort/protocol

Also, tests are added to test the added behavior.

The logic of auto populating names for the services has been moved
from fix.go to populators.go, because now the auto population
happens once the []ServicePort is populated from the both the
inputs, ServiceSpecMod.Ports and ServiceSpecMod.PortMappings

Fixes #216

@@ -30,6 +30,8 @@ services:
- port: 8080
targetPort: 80
endpoint: minikube.external/foo
portMappings:
Copy link
Member

Choose a reason for hiding this comment

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

this doesn't make sense in this example, as there is nothing running on port 8081

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated

Copy link
Member

Choose a reason for hiding this comment

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

I don't know if this makes sense, why would you expose the same port twice?

I think that all our examples like this have to work and make sense.

I don't think that showing one example with all Kedge keys has any benefit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree, but I think we should cover all the fields in file-reference.md so we can show them in use. Where do you suggest we add that?
I think we should have an example which shows all the fields added/modified by kedge in file-reference, it does not have to be a working example, but something that has all the fields and acts as a quick reference on how and where to use that field.

As for this, should I remove this?

Copy link
Member

Choose a reason for hiding this comment

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

I think that if we show an example with all the fields it will be just hard to read and it won't provide any benefits.

I think that far better is to have multiple examples highlighting given shortcut in the scenario that is best for it. We should show how it should be used, in the scenarios we think is the best for given shortcut and highlight our intent behind it.

Having multiple examples in files-reference.md will be also mess, so we can just reference examples in docs/examples/

In this case, we can just link to docs/examples/portMappings/httpd.yaml with some short explanation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

- <port:targetPort/protocol>
```

The only field mandatory here is "port".
Copy link
Member

Choose a reason for hiding this comment

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

Can you specify that you are talking about port part in the portMappings. When I read this for the first time I thought you are talking about port in Service.

How about this?
'The only mandatory part of the portMappings is port'

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

makes sense, updated

@@ -30,6 +30,8 @@ services:
- port: 8080
targetPort: 80
endpoint: minikube.external/foo
portMappings:
- 8888:80/TCPf
Copy link
Member

Choose a reason for hiding this comment

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

s/TCPf/TCP/

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

whoops, fixed

@kadel
Copy link
Member

kadel commented Sep 11, 2017

Needs rebase, but otherwise this PR LGTM

@concaf
Copy link
Collaborator Author

concaf commented Sep 12, 2017

@kadel rebased

- name: httpd
portMappings:
- 8080:80/TCP
```
Copy link
Member

Choose a reason for hiding this comment

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

Can you also mention under known issues or some kinda note saying if you are using portMappings you cannot use the shortcut expose for creating ingress ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is that a known issue? endpoint shortcut is only supported with ports: field, and portMappings: is at the same level and not nested under ports. It's not something that's not supported, it's something that's not part of the syntax of portMappings.

Copy link
Member

Choose a reason for hiding this comment

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

I just thought it would be better if we be explicit about it.

containers:
- image: centos/httpd
services:
- name: httpd
Copy link
Member

Choose a reason for hiding this comment

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

nit: extra indentation in this section!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

api_v1 "k8s.io/client-go/pkg/api/v1"
"strconv"
Copy link
Member

Choose a reason for hiding this comment

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

can you arrange the imports similar to the comment here #149 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

@@ -150,6 +167,93 @@ func populateEnvFrom(c Container, cms []ConfigMapMod, secrets []SecretMod) (Cont
return c, nil
}

// Parse the string the get the port, targetPort and protocol
// information, and then return the resulting ServicePort object
func parsePortMapping(pm string) (*api_v1.ServicePort, error) {
Copy link
Member

Choose a reason for hiding this comment

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

the placement of this function is wrong can, you place it in resources.go or util.go ? Because this is file of all the populators !

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

well, this is parsing the port mapping and populating a ServicePort. What do we really mean by a populator? Will help in moving this around.

Copy link
Member

Choose a reason for hiding this comment

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

this function is just parsing, and not populating so logically it should reside somewhere but not with all other populatiors.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ack, moved to resources.go

}
}

func TestParsePortMapping(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

the placement of this function is wrong can, you place it in resources.go or util.go ? Because this is file of all the populators !

Since the code will move so should the tests!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

moved

// Only populate if the port name is not already specified
if len(servicePorts[i].Name) == 0 {
servicePorts[i].Name = serviceName + "-" + strconv.FormatInt(int64(servicePorts[i].Port), 10)
fmt.Println(servicePorts[i].Name)
Copy link
Member

Choose a reason for hiding this comment

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

Also remove this debug statement!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

whoops, removed

@@ -21,6 +21,9 @@ import (
"sort"
"testing"

"fmt"
Copy link
Member

Choose a reason for hiding this comment

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

can you arrange the imports similar to the comment here #149 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

switch test.success {
case true:
if err != nil {
t.Errorf("Expected test to pass but got an error -\n%v", err)
Copy link
Member

Choose a reason for hiding this comment

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

this is test failure situtation and you are in a function of a test, so use t.Fatal.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was actually confused why are doing a t.Fatalf for rest of the tests.

  • t.Error marks the function as having failed but continues execution. (FWIW, 2,611 code results in kubernetes/kubernetes)
  • t.Fatal marks the function as having failed and stops its execution. (FWIW, 596 code results in kubernetes/kubernetes)

There are other checks which should continue to happen in the function if one test fails so the resulting test report is more comprehensive.

Copy link
Member

Choose a reason for hiding this comment

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

Each test here runs in separate function when you do t.Run() so you have to do t.Fatal() or else t.Error() and return.

Copy link
Member

Choose a reason for hiding this comment

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

There are other checks which should continue to happen in the function if one test fails so the resulting test report is more comprehensive.

One test fail won't affect other tests, they will run as usual, since each test is running in its own test function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, t.Errorf does not require to return.

One test fail won't affect other tests, they will run as usual, since each test is running in its own test function.

It does not affect other tests, but the further checks for the same test do not happen when t.Fatal is used. This cuts down the test reports and does not give a full report of failing tests.

Copy link
Member

Choose a reason for hiding this comment

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

What is the guarantee that the results we get after a failing condition are correct ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not about a guarantee, this is about getting the most extensive test report to troubleshoot the test failures. It does not make sense to fix one test, and then run tests again to find out that another one is failing, and so on.
It's also the used convention, rightfully so, in Kubernetes as well.

}
}

if !reflect.DeepEqual(sp, test.servicePort) {
Copy link
Member

Choose a reason for hiding this comment

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

we need this check only when tests pass which is case true

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For a general testing pattern, if we add a case where we need to test the function output even when an error occurs, we will miss that if we check for this inside case: true.
e.g.

		{
			name:        "something",
			portMapping: "1337:1338::1339/TCP",
			servicePort: &api_v1.ServicePort{},
			success:     false,
		},

I think we should keep it outside.

Copy link
Member

Choose a reason for hiding this comment

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

We can make changes when we need it like that, right now we don't!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

unsure what we lose by keeping it this way

Copy link
Member

Choose a reason for hiding this comment

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

It good to add things when we need them!

}
case false:
if err == nil {
t.Errorf("Expected %v to fail, but test passed!", test.portMapping)
Copy link
Member

Choose a reason for hiding this comment

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

this is test failure situtation and you are in a function of a test, so use t.Fatal.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

see above

@surajssd
Copy link
Member

Since this is a feature, can you add a e2e test which has two ports specified and the test checks if both of them are serving ?

This commit adds a shortcut to ServiceSpec called portMappings.
This lets us set the port, targetPort and the protocol for a
service in a single line. This is parsed and converted to a
Kubernetes ServicePort object.

portMappings is an array of `port:targetPort/protocol`
definitions, so the syntax looks like -

portMappings:
- <port:targetPort/protocol>
- <port:targetPort/protocol>

Also, tests are added to test the added behavior.

The logic of auto populating names for the services has been moved
from fix.go to populators.go, because now the auto population
happens once the []ServicePort is populated from the both the
inputs, ServiceSpecMod.Ports and ServiceSpecMod.PortMappings

Fixes kedgeproject#216
}

if !reflect.DeepEqual(sp, test.servicePort) {
t.Errorf("Expected ServicePort to be -\n%v\nBut got -\n%v", spew.Sprint(test.servicePort), spew.Sprint(sp))
Copy link
Member

Choose a reason for hiding this comment

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

Forked the discussion from #242 (comment)

Consider a case if the test is expected to pass, but it fails then the test should not go any further than

t.Errorf("Expected test to pass but got an error -\n%v", err)

which is line #3315

because we are sure that it will surely throw error at line #324 which is

t.Errorf("Expected ServicePort to be -\n%v\nBut got -\n%v", spew.Sprint(test.servicePort), spew.Sprint(sp))

because the variable sp is nil because of failed test. So if we know further in path if a test is surely gonna fail, I don't think there is point in continuing atleast in this case. Because that won't be comprehensive test output but useless verbose output on a sure thing that is always gonna happen.


For e.g. consider:

$ go test -v github.com/kedgeproject/kedge/pkg/spec -run TestParsePortMapping
=== RUN   TestParsePortMapping
=== RUN   TestParsePortMapping/Nothing_is_passed,_not_even_port
=== RUN   TestParsePortMapping/Only_'port'_is_passed
=== RUN   TestParsePortMapping/port:targetPort_is_passed
=== RUN   TestParsePortMapping/port/protocol_is_passed
=== RUN   TestParsePortMapping/port:targetPort/protocol_is_passed
=== RUN   TestParsePortMapping/Invalid_protocol_(neither_TCP_nor_UDP)_is_passed
=== RUN   TestParsePortMapping/Multiple_protocols_passed,_multiple_'/'_test
=== RUN   TestParsePortMapping/Non_int_port_is_passed
=== RUN   TestParsePortMapping/Non_int_targetPort_is_passed
=== RUN   TestParsePortMapping/More_than_2_ports_passed,_multiple_':'_test
--- FAIL: TestParsePortMapping (0.00s)
    --- PASS: TestParsePortMapping/Nothing_is_passed,_not_even_port (0.00s)
    --- PASS: TestParsePortMapping/Only_'port'_is_passed (0.00s)
    --- PASS: TestParsePortMapping/port:targetPort_is_passed (0.00s)
    --- PASS: TestParsePortMapping/port/protocol_is_passed (0.00s)
    --- FAIL: TestParsePortMapping/port:targetPort/protocol_is_passed (0.00s)
        resources_test.go:315: Expected test to pass but got an error -
                port is not an int: strconv.ParseInt: parsing "": invalid syntax
        resources_test.go:324: Expected ServicePort to be -
                <*>&ServicePort{Name:,Protocol:UDP,Port:1337,TargetPort:1338,NodePort:0,}
                But got -
                <nil>
    --- PASS: TestParsePortMapping/Invalid_protocol_(neither_TCP_nor_UDP)_is_passed (0.00s)
    --- PASS: TestParsePortMapping/Multiple_protocols_passed,_multiple_'/'_test (0.00s)
    --- PASS: TestParsePortMapping/Non_int_port_is_passed (0.00s)
    --- PASS: TestParsePortMapping/Non_int_targetPort_is_passed (0.00s)
    --- PASS: TestParsePortMapping/More_than_2_ports_passed,_multiple_':'_test (0.00s)
FAIL
exit status 1
FAIL    github.com/kedgeproject/kedge/pkg/spec  0.044s

So for failed test only this is sufficient

resources_test.go:315: Expected test to pass but got an error -
                port is not an int: strconv.ParseInt: parsing "": invalid syntax

We don't need to have the next set of lines:

        resources_test.go:324: Expected ServicePort to be -
                <*>&ServicePort{Name:,Protocol:UDP,Port:1337,TargetPort:1338,NodePort:0,}
                But got -
                <nil>

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A function like the following is being tested -

func a() {
  behavior x
  behavior y
  behavior z
}

We write a test -

func TestA() {
  t.Run("test a()", func(t *testing.T) {
    t.Fatal("fail if behavior x fails")
    t.Fatal("fail if behavior y fails")
    t.Fatal("fail if behavior z fails")
  }
}

Now, when behavior x fails, behavior y and behavior z will not be tested, in our test report we will only get failure for behavior x. When we fix that, we will then get an error for behavior y, then after fixing that we get the report for behavior z.

OTOH, if we use -

func TestA() {
  t.Run("test a()", func(t *testing.T) {
    t.Error("fail if behavior x fails")
    t.Error("fail if behavior y fails")
    t.Error("fail if behavior z fails")
  }
}

In this case we will get test failure reports for all the behaviors x, y and z at the same time and we can go ahead and do the fixes simultaneously.

The point of a test report is to find all the failures happening in the tests and making the report as extensive as we can so we can get what's breaking what, instead not to find failures in an iterative manner.

services:
- name: httpd
portMappings:
- 8080:80/TCP
Copy link
Member

Choose a reason for hiding this comment

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

Similar to #242 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed

api_v1 "k8s.io/client-go/pkg/api/v1"

"github.com/pkg/errors"
Copy link
Member

Choose a reason for hiding this comment

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

can you arrange the imports similar to the comment here #149 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There are no kedge imports here, these are in the order of stdlib first, then third party. What are you suggesting?

"reflect"
"sort"
"testing"

api_v1 "k8s.io/client-go/pkg/api/v1"

Copy link
Member

Choose a reason for hiding this comment

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

nit: s/\n//

can you arrange the imports similar to the comment here #149 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is similar to what's done here - https://github.com/kedgeproject/kedge/blob/master/pkg/spec/resources.go#L19-L38 - what's wrong?

Copy link
Member

@surajssd surajssd left a comment

Choose a reason for hiding this comment

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

Since this is a feature, can you add a e2e test which has two ports specified and the test checks if both of them are serving ?

@surajssd surajssd merged commit 38b9d03 into kedgeproject:master Sep 14, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

shortcut for port
3 participants