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

feat: add native GRPC support #454

Merged
merged 2 commits into from
Nov 11, 2019
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
4 changes: 4 additions & 0 deletions deploy/manifests/base/custom-types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
https_redirect_status_code:
type: integer
proxy:
Expand All @@ -181,6 +183,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
path:
type: string
pattern: ^/.*$
Expand Down
2 changes: 2 additions & 0 deletions deploy/manifests/base/kong-ingress-dbless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ spec:
value: /dev/stderr
- name: KONG_ADMIN_LISTEN
value: 127.0.0.1:8444 ssl
- name: KONG_PROXY_LISTEN
value: 0.0.0.0:8000, 0.0.0.0:8443 ssl http2
lifecycle:
preStop:
exec:
Expand Down
6 changes: 6 additions & 0 deletions deploy/single/all-in-one-dbless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
read_timeout:
minimum: 0
Expand Down Expand Up @@ -131,6 +133,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
type: array
regex_priority:
Expand Down Expand Up @@ -511,6 +515,8 @@ spec:
value: /dev/stderr
- name: KONG_ADMIN_LISTEN
value: 127.0.0.1:8444 ssl
- name: KONG_PROXY_LISTEN
value: 0.0.0.0:8000, 0.0.0.0:8443 ssl http2
image: kong:1.3
lifecycle:
preStop:
Expand Down
6 changes: 6 additions & 0 deletions deploy/single/all-in-one-postgres-enterprise.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
read_timeout:
minimum: 0
Expand Down Expand Up @@ -131,6 +133,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
type: array
regex_priority:
Expand Down Expand Up @@ -534,6 +538,8 @@ spec:
value: /dev/stderr
- name: KONG_ADMIN_LISTEN
value: 127.0.0.1:8444 ssl
- name: KONG_PROXY_LISTEN
value: 0.0.0.0:8000, 0.0.0.0:8443 ssl http2
image: kong-docker-kong-enterprise-edition-docker.bintray.io/kong-enterprise-edition:0.36-2-alpine
lifecycle:
preStop:
Expand Down
6 changes: 6 additions & 0 deletions deploy/single/all-in-one-postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
read_timeout:
minimum: 0
Expand Down Expand Up @@ -131,6 +133,8 @@ spec:
enum:
- http
- https
- grpc
- grpcs
type: string
type: array
regex_priority:
Expand Down Expand Up @@ -529,6 +533,8 @@ spec:
value: /dev/stderr
- name: KONG_ADMIN_LISTEN
value: 127.0.0.1:8444 ssl
- name: KONG_PROXY_LISTEN
value: 0.0.0.0:8000, 0.0.0.0:8443 ssl http2
image: kong:1.3
lifecycle:
preStop:
Expand Down
21 changes: 17 additions & 4 deletions internal/ingress/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ const (

configurationAnnotationKey = "configuration.konghq.com"

protocolAnnotationKey = "configuration.konghq.com/protocol"

protocolsAnnotationKey = "configuration.konghq.com/protocols"

// DefaultIngressClass defines the default class used
// by Kong's ingres controller.
// by Kong's ingress controller.
DefaultIngressClass = "kong"
)

Expand All @@ -48,7 +52,7 @@ func validIngress(ingressAnnotationValue, ingressClass string) bool {
return ingressAnnotationValue == ingressClass
}

// IngressClassValidatorFunc returns a function which can valid if an object
// IngressClassValidatorFunc returns a function which can validate if an Object
// belongs to an the ingressClass or not.
func IngressClassValidatorFunc(
ingressClass string) func(obj metav1.Object) bool {
Expand All @@ -60,8 +64,7 @@ func IngressClassValidatorFunc(
}

// IngressClassValidatorFuncFromObjectMeta returns a function which
// can valid if an ObjectMeta
// belongs to an the ingressClass or not.
// can validate if an ObjectMeta belongs to an the ingressClass or not.
func IngressClassValidatorFuncFromObjectMeta(
ingressClass string) func(obj *metav1.ObjectMeta) bool {

Expand Down Expand Up @@ -95,6 +98,16 @@ func ExtractConfigurationName(anns map[string]string) string {
return anns[configurationAnnotationKey]
}

// ExtractProtocolName extracts the protocol supplied in the annotation
func ExtractProtocolName(anns map[string]string) string {
return anns[protocolAnnotationKey]
}

// ExtractProtocolNames extracts the protocols supplied in the annotation
func ExtractProtocolNames(anns map[string]string) []string {
return strings.Split(anns[protocolsAnnotationKey], ",")
}

// HasServiceUpstreamAnnotation returns true if the annotation
// ingress.kubernetes.io/service-upstream is set to "true" in anns.
func HasServiceUpstreamAnnotation(anns map[string]string) bool {
Expand Down
24 changes: 24 additions & 0 deletions internal/ingress/annotations/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package annotations

import (
"reflect"
"testing"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -49,6 +50,29 @@ func TestExtractConfigurationName(t *testing.T) {
}
}

func TestExtractProtocolName(t *testing.T) {
data := map[string]string{
"configuration.konghq.com/protocol": "grpc",
}

pn := ExtractProtocolName(data)
if pn != "grpc" {
t.Errorf("expected grpc as configuration name but got %v", pn)
}
}

func TestExtractProtocolNames(t *testing.T) {
data := map[string]string{
"configuration.konghq.com/protocols": "grpc,grpcs",
}

s := []string{"grpc", "grpcs"}

pns := ExtractProtocolNames(data)
if !reflect.DeepEqual(pns, s) {
t.Errorf("expected grpc,grpcs as configuration name but got %v", pns)
}
}
func TestIngrssClassValidatorFunc(t *testing.T) {
tests := []struct {
ingress string
Expand Down
134 changes: 116 additions & 18 deletions internal/ingress/controller/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"reflect"
"regexp"
"sort"
"strings"

Expand Down Expand Up @@ -110,6 +111,8 @@ var supportedCreds = sets.NewString(
"oauth2",
)

var validProtocols = regexp.MustCompile(`\Ahttps$|\Ahttp$|\Agrpc$|\Agrpcs$`)

// New returns a new parser backed with store.
func New(store store.Storer) Parser {
return Parser{store: store}
Expand Down Expand Up @@ -497,26 +500,30 @@ func (p *Parser) parseIngressRules(
func (p *Parser) fillOverrides(state KongState) error {
for i := 0; i < len(state.Services); i++ {
// Services
var anns map[string]string
svc, err := p.store.GetService(
state.Services[i].Namespace,
state.Services[i].Backend.ServiceName)
if err != nil {
glog.Errorf("error getting services %v", err)
}
anns = svc.Annotations
kongIngress, err := p.getKongIngressForService(
state.Services[i].Namespace, state.Services[i].Backend.ServiceName)
if err == nil {
overrideService(&state.Services[i], kongIngress)
} else {
glog.Error(errors.Wrapf(err, "fetching KongIngress for service %v/%v",
state.Services[i].Namespace,
state.Services[i].Backend.ServiceName))
state.Services[i].Namespace,
state.Services[i].Backend.ServiceName)
if err != nil {
glog.Errorf("error getting kongIngress %v", err)
}
overrideService(&state.Services[i], kongIngress, anns)

// Routes
for j := 0; j < len(state.Services[i].Routes); j++ {
kongIngress, err := p.getKongIngressFromIngress(
&state.Services[i].Routes[j].Ingress)
if err == nil {
overrideRoute(&state.Services[i].Routes[j], kongIngress)
} else {
glog.Error(errors.Wrapf(err, "fetching KongIngress for Ingress '%v' in namespace '%v'",
state.Services[i].Routes[j].Ingress.Name, state.Services[i].Routes[j].Ingress.Namespace))
if err != nil {
glog.Errorf("error getting kongIngress %v", err)
}
overrideRoute(&state.Services[i].Routes[j], kongIngress)
}
}

Expand All @@ -534,9 +541,10 @@ func (p *Parser) fillOverrides(state KongState) error {
return nil
}

func overrideService(service *Service,
// overrideServiceByKongIngress sets Service fields by KongIngress
func overrideServiceByKongIngress(service *Service,
kongIngress *configurationv1.KongIngress) {
if service == nil || kongIngress == nil || kongIngress.Proxy == nil {
if kongIngress == nil || kongIngress.Proxy == nil {
return
}
s := kongIngress.Proxy
Expand All @@ -560,13 +568,40 @@ func overrideService(service *Service,
}
}

func overrideRoute(route *Route,
// overrideServiceByAnnotation sets the Service protocol via annotation
func overrideServiceByAnnotation(service *Service,
anns map[string]string) {
protocol := annotations.ExtractProtocolName(anns)
if protocol == "" || validateProtocol(protocol) != true {
return
}
service.Protocol = kong.String(protocol)
}

// overrideService sets Service fields by KongIngress first, then by annotation
func overrideService(service *Service,
kongIngress *configurationv1.KongIngress,
anns map[string]string) {
if service == nil {
return
}
overrideServiceByKongIngress(service, kongIngress)
overrideServiceByAnnotation(service, anns)

if *service.Protocol == "grpc" || *service.Protocol == "grpcs" {
// grpc(s) doesn't accept a path
service.Path = nil
}
}

// overrideRouteByKongIngress sets Route fields by KongIngress
func overrideRouteByKongIngress(route *Route,
kongIngress *configurationv1.KongIngress) {
if route == nil || kongIngress == nil || kongIngress.Route == nil {
if kongIngress == nil || kongIngress.Route == nil {
return
}
r := kongIngress.Route

r := kongIngress.Route
if len(r.Methods) != 0 {
route.Methods = cloneStringPointerSlice(r.Methods...)
}
Expand All @@ -590,6 +625,69 @@ func overrideRoute(route *Route,
}
}

// normalizeProtocols prevents users from mismatching grpc/http
func normalizeProtocols(route *Route) {
protocols := route.Protocols
var http, grpc bool

for _, protocol := range protocols {
if strings.Contains(*protocol, "grpc") {
grpc = true
}
if strings.Contains(*protocol, "http") {
http = true
}
if validateProtocol(*protocol) != true {
http = true
}
}

if grpc && http {
route.Protocols = kong.StringSlice("http", "https")
}
}

// validateProtocol returns a bool of whether string is a valid protocol
func validateProtocol(protocol string) bool {
match := validProtocols.MatchString(protocol)
return match
}

// overrideRouteByAnnotation sets Route protocols via annotation
func overrideRouteByAnnotation(route *Route, anns map[string]string) {
if anns == nil {
return
}
protocols := annotations.ExtractProtocolNames(anns)
var prots []*string
for _, prot := range protocols {
if validateProtocol(prot) != true {
return
}
prots = append(prots, kong.String(prot))
}

route.Protocols = prots
}

// overrideRoute sets Route fields by KongIngress first, then by annotation
func overrideRoute(route *Route,
kongIngress *configurationv1.KongIngress) {
if route == nil {
return
}
overrideRouteByKongIngress(route, kongIngress)
overrideRouteByAnnotation(route, route.Ingress.Annotations)
normalizeProtocols(route)
for _, val := range route.Protocols {
if *val == "grpc" || *val == "grpcs" {
// grpc(s) doesn't accept strip_path
route.StripPath = nil
break
}
}
}

func cloneStringPointerSlice(array ...*string) []*string {
var res []*string
for _, s := range array {
Expand Down Expand Up @@ -854,7 +952,7 @@ func (p *Parser) getKongIngressForService(namespace, serviceName string) (
return p.store.GetKongIngress(svc.Namespace, confName)
}

// getKongIngress checks if the Ingress contains an annotation for configuration
// getKongIngressFromIngress checks if the Ingress contains an annotation for configuration
// or if exists a KongIngress object with the same name than the Ingress
func (p *Parser) getKongIngressFromIngress(ing *networking.Ingress) (
*configurationv1.KongIngress, error) {
Expand Down
Loading