提交 676062d7 编写于 作者: Z zackzhangkai

support virtualservice multi port

Signed-off-by: Nzackzhangkai <zhangkaiamm@gmail.com>
上级 6f719e0a
......@@ -118,6 +118,7 @@ func FillDestinationPort(vs *v1alpha3.VirtualService, service *corev1.Service) {
vs.Spec.Http[i].Route[j].Destination.Port = &apiv1alpha3.PortSelector{
Number: uint32(service.Spec.Ports[0].Port),
}
vs.Spec.Http[i].Match[j].Port = uint32(service.Spec.Ports[0].Port)
}
}
......@@ -135,7 +136,18 @@ func FillDestinationPort(vs *v1alpha3.VirtualService, service *corev1.Service) {
vs.Spec.Tcp[i].Route[j].Destination.Port = &apiv1alpha3.PortSelector{
Number: uint32(service.Spec.Ports[0].Port),
}
vs.Spec.Tcp[i].Match[j].Port = uint32(service.Spec.Ports[0].Port)
}
}
}
}
func SupportHttpProtocol(protocol string) bool {
httpPro := []string{"http", "http2", "grpc"}
for _, i := range httpPro {
if strings.ToLower(protocol) == i || strings.HasPrefix(strings.ToLower(protocol), i+"-") {
return true
}
}
return false
}
package servicemesh
import "testing"
func TestIsHTTP(t *testing.T) {
if !SupportHttpProtocol("gRPC") {
t.Errorf("gRPC is HTTP protocol")
}
if !SupportHttpProtocol("HTTP") {
t.Errorf("HTTP is HTTP protocol")
}
if !SupportHttpProtocol("HTTP2") {
t.Errorf("HTTP2 is HTTP protocol")
}
if SupportHttpProtocol("Mysql") {
t.Errorf("mysql is not HTTP protocol")
}
if SupportHttpProtocol("udp") {
t.Errorf("UDP is not HTTP protocol")
}
}
......@@ -19,10 +19,6 @@ package virtualservice
import (
"context"
"fmt"
"kubesphere.io/kubesphere/pkg/controller/utils/servicemesh"
"reflect"
"strings"
apinetworkingv1alpha3 "istio.io/api/networking/v1alpha3"
clientgonetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
istioclient "istio.io/client-go/pkg/clientset/versioned"
......@@ -49,6 +45,8 @@ import (
servicemeshclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
servicemeshinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/servicemesh/v1alpha2"
servicemeshlisters "kubesphere.io/kubesphere/pkg/client/listers/servicemesh/v1alpha2"
"kubesphere.io/kubesphere/pkg/controller/utils/servicemesh"
"reflect"
"time"
)
......@@ -320,9 +318,13 @@ func (v *VirtualServiceController) syncService(key string) error {
// TODO(jeff): use FQDN to replace service name
vs.Spec.Hosts = []string{name}
vs.Spec.Http = []*apinetworkingv1alpha3.HTTPRoute{}
vs.Spec.Tcp = []*apinetworkingv1alpha3.TCPRoute{}
// check if service has TCP protocol ports
for _, port := range service.Spec.Ports {
var route apinetworkingv1alpha3.HTTPRouteDestination
var match apinetworkingv1alpha3.HTTPMatchRequest
if port.Protocol == v1.ProtocolTCP {
route = apinetworkingv1alpha3.HTTPRouteDestination{
Destination: &apinetworkingv1alpha3.Destination{
......@@ -335,22 +337,29 @@ func (v *VirtualServiceController) syncService(key string) error {
Weight: 100,
}
match = apinetworkingv1alpha3.HTTPMatchRequest{Port: uint32(port.Port)}
// a http port, add to HTTPRoute
if len(port.Name) > 0 && (port.Name == "http" || strings.HasPrefix(port.Name, "http-")) {
vs.Spec.Http = []*apinetworkingv1alpha3.HTTPRoute{{Route: []*apinetworkingv1alpha3.HTTPRouteDestination{&route}}}
break
}
// everything else treated as TCPRoute
tcpRoute := apinetworkingv1alpha3.TCPRoute{
Route: []*apinetworkingv1alpha3.RouteDestination{
{
Destination: route.Destination,
Weight: route.Weight,
if servicemesh.SupportHttpProtocol(port.Name) {
httpRoute := apinetworkingv1alpha3.HTTPRoute{
Route: []*apinetworkingv1alpha3.HTTPRouteDestination{&route},
Match: []*apinetworkingv1alpha3.HTTPMatchRequest{&match},
}
vs.Spec.Http = append(vs.Spec.Http, &httpRoute)
} else {
// everything else treated as TCPRoute
tcpRoute := apinetworkingv1alpha3.TCPRoute{
Route: []*apinetworkingv1alpha3.RouteDestination{
{
Destination: route.Destination,
Weight: route.Weight,
},
},
},
Match: []*apinetworkingv1alpha3.L4MatchAttributes{{Port: match.Port}},
}
vs.Spec.Tcp = append(vs.Spec.Tcp, &tcpRoute)
}
vs.Spec.Tcp = []*apinetworkingv1alpha3.TCPRoute{&tcpRoute}
}
}
......@@ -385,7 +394,6 @@ func (v *VirtualServiceController) syncService(key string) error {
default:
vs.Spec = v.generateVirtualServiceSpec(strategies[0], service).Spec
}
}
createVirtualService := len(currentVirtualService.ResourceVersion) == 0
......@@ -542,8 +550,40 @@ func (v *VirtualServiceController) getSubsets(strategy *servicemeshv1alpha2.Stra
func (v *VirtualServiceController) generateVirtualServiceSpec(strategy *servicemeshv1alpha2.Strategy, service *v1.Service) *clientgonetworkingv1alpha3.VirtualService {
// Define VirtualService to be created
vs := &clientgonetworkingv1alpha3.VirtualService{
Spec: strategy.Spec.Template.Spec,
vs := &clientgonetworkingv1alpha3.VirtualService{}
vs.Spec.Hosts = strategy.Spec.Template.Spec.Hosts
// For multi-ports, apply the rules to each port matched http/tcp protocol
for _, port := range service.Spec.Ports {
s := strategy.DeepCopy()
strategyTempSpec := s.Spec.Template.Spec
// fill route.destination.port and match.port filed
if len(strategyTempSpec.Http) > 0 && servicemesh.SupportHttpProtocol(port.Name) {
for _, http := range strategyTempSpec.Http {
if len(http.Match) == 0 {
http.Match = []*apinetworkingv1alpha3.HTTPMatchRequest{{Port: uint32(port.Port)}}
} else {
for _, match := range http.Match {
match.Port = uint32(port.Port)
}
}
for _, route := range http.Route {
route.Destination.Port = &apinetworkingv1alpha3.PortSelector{
Number: uint32(port.Port),
}
}
}
vs.Spec.Http = append(vs.Spec.Http, strategyTempSpec.Http...)
}
if len(strategyTempSpec.Tcp) > 0 && !servicemesh.SupportHttpProtocol(port.Name) {
for _, tcp := range strategyTempSpec.Tcp {
tcp.Match = []*apinetworkingv1alpha3.L4MatchAttributes{{Port: uint32(port.Port)}}
for _, r := range tcp.Route {
r.Destination.Port = &apinetworkingv1alpha3.PortSelector{Number: uint32(port.Port)}
}
}
vs.Spec.Tcp = append(vs.Spec.Tcp, strategyTempSpec.Tcp...)
}
}
// one version rules them all
......@@ -556,29 +596,34 @@ func (v *VirtualServiceController) generateVirtualServiceSpec(strategy *servicem
Weight: 100,
}
if len(strategy.Spec.Template.Spec.Http) > 0 {
governorRoute := apinetworkingv1alpha3.HTTPRoute{
Route: []*apinetworkingv1alpha3.HTTPRouteDestination{&governorDestinationWeight},
}
for _, port := range service.Spec.Ports {
match := apinetworkingv1alpha3.HTTPMatchRequest{Port: uint32(port.Port)}
if len(strategy.Spec.Template.Spec.Http) > 0 {
governorRoute := apinetworkingv1alpha3.HTTPRoute{
Route: []*apinetworkingv1alpha3.HTTPRouteDestination{&governorDestinationWeight},
Match: []*apinetworkingv1alpha3.HTTPMatchRequest{&match},
}
vs.Spec.Http = []*apinetworkingv1alpha3.HTTPRoute{&governorRoute}
vs.Spec.Http = []*apinetworkingv1alpha3.HTTPRoute{&governorRoute}
} else if len(strategy.Spec.Template.Spec.Tcp) > 0 {
tcpRoute := apinetworkingv1alpha3.TCPRoute{
Route: []*apinetworkingv1alpha3.RouteDestination{
{
Destination: &apinetworkingv1alpha3.Destination{
Host: governorDestinationWeight.Destination.Host,
Subset: governorDestinationWeight.Destination.Subset,
}
if len(strategy.Spec.Template.Spec.Tcp) > 0 {
tcpRoute := apinetworkingv1alpha3.TCPRoute{
Route: []*apinetworkingv1alpha3.RouteDestination{
{
Destination: &apinetworkingv1alpha3.Destination{
Host: governorDestinationWeight.Destination.Host,
Subset: governorDestinationWeight.Destination.Subset,
},
Weight: governorDestinationWeight.Weight,
},
Weight: governorDestinationWeight.Weight,
},
},
}
Match: []*apinetworkingv1alpha3.L4MatchAttributes{{Port: match.Port}},
}
//governorRoute := v1alpha3.TCPRoute{tcpRoute}
vs.Spec.Tcp = []*apinetworkingv1alpha3.TCPRoute{&tcpRoute}
//governorRoute := v1alpha3.TCPRoute{tcpRoute}
vs.Spec.Tcp = []*apinetworkingv1alpha3.TCPRoute{&tcpRoute}
}
}
}
servicemesh.FillDestinationPort(vs, service)
......
......@@ -45,6 +45,9 @@ var (
applicationName = "bookinfo"
namespace = metav1.NamespaceDefault
subsets = []string{"v1", "v2"}
httpPort = 80
grpcPort = 81
mysqlPort = 82
)
type fixture struct {
......@@ -123,7 +126,7 @@ func newVirtualService(name string, host string, labels map[string]string) *v1al
return &vr
}
func newService(name string, labels map[string]string, selector map[string]string, protocol string, port int) *v1.Service {
func newService(name string, labels map[string]string, selector map[string]string) *v1.Service {
svc := v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
......@@ -134,9 +137,21 @@ func newService(name string, labels map[string]string, selector map[string]strin
Ports: []v1.ServicePort{
{
Protocol: v1.ProtocolTCP,
Port: int32(port),
Name: fmt.Sprintf("%s-aaa", protocol),
TargetPort: intstr.FromInt(port),
Port: int32(httpPort),
Name: "HTTP-1",
TargetPort: intstr.FromInt(httpPort),
},
{
Protocol: v1.ProtocolTCP,
Port: int32(grpcPort),
Name: "grpc-1",
TargetPort: intstr.FromInt(grpcPort),
},
{
Protocol: v1.ProtocolTCP,
Port: int32(mysqlPort),
Name: "mysql-1",
TargetPort: intstr.FromInt(mysqlPort),
},
},
Selector: selector,
......@@ -299,7 +314,7 @@ func (f *fixture) run_(serviceKey string, expectedVS *v1alpha3.VirtualService, s
func TestInitialStrategyCreate(t *testing.T) {
f := newFixture(t)
svc := newService("foo", NewLabels().WithApplication(applicationName).WithApp(serviceName), NewLabels().WithApplication(serviceName).WithApp(applicationName), "http", 80)
svc := newService("foo", NewLabels().WithApplication(applicationName).WithApp(serviceName), NewLabels().WithApplication(serviceName).WithApp(applicationName))
dr := newDestinationRule(svc.Name, toHost(svc), NewLabels().WithApp("foo").WithApplication(applicationName), subsets[0])
svc.Annotations = NewLabels().WithServiceMeshEnabled(true)
......@@ -310,21 +325,46 @@ func TestInitialStrategyCreate(t *testing.T) {
vs := newVirtualService(svc.Name, "foo", NewLabels().WithApplication("bookinfo").WithApp(svc.Name))
vs.Annotations = make(map[string]string)
vs.Spec.Http = []*apiv1alpha3.HTTPRoute{
{
Route: []*apiv1alpha3.HTTPRouteDestination{
{
Destination: &apiv1alpha3.Destination{
Host: svc.Name,
Subset: "v1",
Port: &apiv1alpha3.PortSelector{
Number: uint32(svc.Spec.Ports[0].Port),
for _, port := range svc.Spec.Ports {
if servicemesh.SupportHttpProtocol(port.Name) {
httpRoute := apiv1alpha3.HTTPRoute{
Route: []*apiv1alpha3.HTTPRouteDestination{
{
Destination: &apiv1alpha3.Destination{
Host: svc.Name,
Subset: "v1",
Port: &apiv1alpha3.PortSelector{
Number: uint32(port.Port),
},
},
Weight: 100,
},
Weight: 100,
},
},
},
Match: []*apiv1alpha3.HTTPMatchRequest{
{Port: uint32(port.Port)},
},
}
vs.Spec.Http = append(vs.Spec.Http, &httpRoute)
} else {
tcpRoute := apiv1alpha3.TCPRoute{
Route: []*apiv1alpha3.RouteDestination{
{
Destination: &apiv1alpha3.Destination{
Host: svc.Name,
Subset: "v1",
Port: &apiv1alpha3.PortSelector{
Number: uint32(port.Port),
},
},
Weight: 100,
},
},
Match: []*apiv1alpha3.L4MatchAttributes{
{Port: uint32(port.Port)},
},
}
vs.Spec.Tcp = append(vs.Spec.Tcp, &tcpRoute)
}
}
key, err := cache.MetaNamespaceKeyFunc(svc)
......@@ -354,7 +394,7 @@ func runStrategy(t *testing.T, svc *v1.Service, dr *v1alpha3.DestinationRule, st
func TestStrategies(t *testing.T) {
svc := newService(serviceName, NewLabels().WithApplication(applicationName).WithApp(serviceName), NewLabels().WithApplication(applicationName).WithApp(serviceName), "http", 80)
svc := newService(serviceName, NewLabels().WithApplication(applicationName).WithApp(serviceName), NewLabels().WithApplication(applicationName).WithApp(serviceName))
defaultDr := newDestinationRule(svc.Name, toHost(svc), NewLabels().WithApp(serviceName).WithApplication(applicationName), subsets...)
svc.Annotations = NewLabels().WithServiceMeshEnabled(true)
defaultStrategy := &v1alpha2.Strategy{
......@@ -395,6 +435,9 @@ func TestStrategies(t *testing.T) {
Weight: 20,
},
},
Match: []*apiv1alpha3.HTTPMatchRequest{
{Port: 0},
},
},
},
},
......@@ -412,7 +455,7 @@ func TestStrategies(t *testing.T) {
Host: svc.Name,
Subset: "v1",
Port: &apiv1alpha3.PortSelector{
Number: uint32(svc.Spec.Ports[0].Port),
Number: uint32(httpPort),
},
},
Weight: 80,
......@@ -422,12 +465,38 @@ func TestStrategies(t *testing.T) {
Host: svc.Name,
Subset: "v2",
Port: &apiv1alpha3.PortSelector{
Number: uint32(svc.Spec.Ports[0].Port),
Number: uint32(httpPort),
},
},
Weight: 20,
},
},
Match: []*apiv1alpha3.HTTPMatchRequest{{Port: uint32(httpPort)}},
},
{
Route: []*apiv1alpha3.HTTPRouteDestination{
{
Destination: &apiv1alpha3.Destination{
Host: svc.Name,
Subset: "v1",
Port: &apiv1alpha3.PortSelector{
Number: uint32(grpcPort),
},
},
Weight: 80,
},
{
Destination: &apiv1alpha3.Destination{
Host: svc.Name,
Subset: "v2",
Port: &apiv1alpha3.PortSelector{
Number: uint32(grpcPort),
},
},
Weight: 20,
},
},
Match: []*apiv1alpha3.HTTPMatchRequest{{Port: uint32(grpcPort)}},
},
}
......@@ -443,6 +512,8 @@ func TestStrategies(t *testing.T) {
expected := defaultExpected.DeepCopy()
expected.Spec.Http[0].Route[0].Weight = 0
expected.Spec.Http[0].Route[1].Weight = 100
expected.Spec.Http[1].Route[0].Weight = 0
expected.Spec.Http[1].Route[1].Weight = 100
runStrategy(t, svc, defaultDr, strategy, expected)
})
......@@ -454,6 +525,7 @@ func TestStrategies(t *testing.T) {
expected.Spec.Http[0].Route[0].Weight = 100
expected.Spec.Http[0].Route[0].Destination.Subset = "v2"
expected.Spec.Http[0].Route = expected.Spec.Http[0].Route[:1]
expected.Spec.Http = expected.Spec.Http[:1]
runStrategy(t, svc, defaultDr, strategy, expected)
})
......@@ -483,6 +555,20 @@ func TestStrategies(t *testing.T) {
Uri: &apiv1alpha3.StringMatch{
MatchType: &apiv1alpha3.StringMatch_Prefix{Prefix: "/apis"},
},
Port: expected.Spec.Http[0].Route[0].Destination.Port.Number,
},
}
expected.Spec.Http[1].Match = []*apiv1alpha3.HTTPMatchRequest{
{
Headers: map[string]*apiv1alpha3.StringMatch{
"X-USER": {
MatchType: &apiv1alpha3.StringMatch_Regex{Regex: "users"},
},
},
Uri: &apiv1alpha3.StringMatch{
MatchType: &apiv1alpha3.StringMatch_Prefix{Prefix: "/apis"},
},
Port: expected.Spec.Http[1].Route[0].Destination.Port.Number,
},
}
runStrategy(t, svc, defaultDr, strategy, expected)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册