diff options
-rw-r--r-- | msb2pilot/src/msb2pilot/Dockerfile | 9 | ||||
-rw-r--r-- | msb2pilot/src/msb2pilot/Gopkg.toml | 8 | ||||
-rw-r--r-- | msb2pilot/src/msb2pilot/pilot/controller.go | 11 | ||||
-rw-r--r-- | msb2pilot/src/msb2pilot/pilot/controller_test.go | 193 | ||||
-rw-r--r-- | msb2pilot/src/msb2pilot/pilot/msb.go | 258 | ||||
-rw-r--r-- | msb2pilot/src/msb2pilot/pilot/msb_test.go | 171 |
6 files changed, 445 insertions, 205 deletions
diff --git a/msb2pilot/src/msb2pilot/Dockerfile b/msb2pilot/src/msb2pilot/Dockerfile new file mode 100644 index 0000000..99b2c9b --- /dev/null +++ b/msb2pilot/src/msb2pilot/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:16.04 + +EXPOSE 9081 + +WORKDIR /home/msb2pilot +COPY ./conf ./conf +COPY ./msb2pilot . + +ENTRYPOINT /home/msb2pilot/msb2pilot diff --git a/msb2pilot/src/msb2pilot/Gopkg.toml b/msb2pilot/src/msb2pilot/Gopkg.toml index eddc881..0759ad5 100644 --- a/msb2pilot/src/msb2pilot/Gopkg.toml +++ b/msb2pilot/src/msb2pilot/Gopkg.toml @@ -27,3 +27,11 @@ [[constraint]] name = "github.com/smartystreets/goconvey" version = "1.6.3" + +[[constraint]] + name = "istio.io/api" + revision = "1.0.0" + +[[constraint]] + name = "istio.io/istio" + revision = "1.0.0" diff --git a/msb2pilot/src/msb2pilot/pilot/controller.go b/msb2pilot/src/msb2pilot/pilot/controller.go index f0cf87d..bc79cc5 100644 --- a/msb2pilot/src/msb2pilot/pilot/controller.go +++ b/msb2pilot/src/msb2pilot/pilot/controller.go @@ -41,7 +41,7 @@ const ( [{}, {}] is error. {} {} is right */ func ParseParam(input string) ([]model.Config, error) { - configs, _, err := crd.ParseInputs(input) + configs, _, err := crd.ParseInputsWithoutValidation(input) return configs, err } @@ -66,12 +66,15 @@ func init() { updateK8sAddress(configPath) var err error - client, err = crd.NewClient(configPath, model.ConfigDescriptor{ - model.RouteRule, - model.DestinationPolicy, + client, err = crd.NewClient(configPath, "", model.ConfigDescriptor{ + model.VirtualService, model.DestinationRule, }, "") + if err = client.RegisterResources(); err != nil { + log.Log.Error("failed to register custom resources.", err) + } + if err != nil { log.Log.Error("fail to init crd", err) } diff --git a/msb2pilot/src/msb2pilot/pilot/controller_test.go b/msb2pilot/src/msb2pilot/pilot/controller_test.go index d7fe7d9..3c01b44 100644 --- a/msb2pilot/src/msb2pilot/pilot/controller_test.go +++ b/msb2pilot/src/msb2pilot/pilot/controller_test.go @@ -13,14 +13,16 @@ package pilot import ( "fmt" - "msb2pilot/models" - "os" - "reflect" + + // "fmt" + // "msb2pilot/models" + // "os" + // "reflect" "testing" ) func TestList(t *testing.T) { - res, err := List("routerules", "default") + res, err := List("virtualservice", "default") if err != nil { t.Errorf("List() => got %v", err) } else { @@ -28,39 +30,162 @@ func TestList(t *testing.T) { } } -func TestUpdateK8sAddress(t *testing.T) { - cases := []struct { - path, addr, want, err string - }{ - { - path: "k8s.yml222", - addr: "filenoteexisttest", - want: "", - err: "*os.PathError", - }, - { - path: configPath, - addr: "", - want: "", - err: "", - }, +//func TestParseParam(t *testing.T) { +// cases := []struct { +// in string +// }{ +// // { +// // in: `{ +// //"apiVersion": "networking.istio.io/v1alpha3", +// //"kind": "VirtualService", +// //"metadata": {"name": "default-apigateway"}, +// //"spec": {"hosts":["apigateway"],"http":[{ +// //"match":{"uri": {"prefix": "/portaladmin"}}, +// //"rewrite": {"uri": "/portaladmin"}, +// //"route": [{"destination": {"host": "portaladmin"}}] +// //},{ +// //"match":{"uri": {"prefix": "/pm_mgt/v1"}}, +// //"rewrite": {"uri": "/pm_mgt/v1"}, +// //"route": [{"destination": {"host": "pm_mgt"}}] +// //}]} +// //}`, +// // }, +// { +// in: `{ +//"apiVersion": "networking.istio.io/v1alpha3", +//"kind": "VirtualService", +//"metadata": {"name": "default-apigateway"}, +//"spec": {"destination":{"service":"reviews.service.consul"},"http":[{ +//"match":{"uri": {"prefix": "/portaladmin"}}, +//"rewrite": {"uri": "/portaladmin"}, +//}]} +//}`, +// }, +// { +// in: `{ +//"apiVersion": "networking.istio.io/v1alpha3", +//"kind": "VirtualService", +//"metadata": {"name": "default-apigateway"}, +//"spec": {"hosts":["test"],"http":[]} +//}`, +// }, +// } + +// for _, cas := range cases { +// res, err := ParseParam(cas.in) +// if err != nil { +// t.Errorf("ParseParam() => got %v", err) +// } else { +// fmt.Print(res) +// } +// } +//} + +func TestCreate(t *testing.T) { + str := ` { - path: configPath, - addr: "k8stest", - want: "k8stest", - err: "", - }, + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata":{ + "name": "reviews"}, + "spec":{ + "hosts":["reviews.service.consul"], + "http":[{ + "match":[{"uri": {"prefix": "/pm_mgt/v1"}}], + "rewrite": {"uri": "/portaladmin"}, + "route":[{ + "destination":{ + "host": "reviews.service.consul", + "subset": "v3" + }}] + }] + } + } + ` + + config, exist := Get("virtualservice", "default", "reviews") + if exist { + Delete("virtualservice", "default", "reviews") + } + configs, err := ParseParam(str) + if err != nil { + t.Errorf("ParseParam() => got %v", err) + } else { + fmt.Println(configs) } - oldEnv := os.Getenv(models.EnvK8sAddress) - for _, cas := range cases { - os.Unsetenv(models.EnvK8sAddress) - os.Setenv(models.EnvK8sAddress, cas.addr) + res, err := Create(&configs[0]) + if err != nil { + t.Errorf("Create() => got %v", err) + } else { + fmt.Println(res) + } - got, err := updateK8sAddress(cas.path) - if got != cas.want || (err != nil && reflect.TypeOf(err).String() != cas.err) { - t.Errorf("updateK8sAddress(%s, %s) => got %s %v, want %s", cas.path, cas.addr, got, reflect.TypeOf(err), cas.want) - } + if exist { + Create(config) } - os.Setenv(models.EnvK8sAddress, oldEnv) } + +//func TestParseParam(t *testing.T) { +// str := ` +// { +// "apiVersion": "networking.istio.io/v1alpha3", +// "kind": "VirtualService", +// "metadata":{ +// "name": "reviews"}, +// "spec":{ +// "hosts":["reviews.service.consul"], +// "http":[{ +// "route":[{ +// "destination":{ +// "host": "reviews.service.consul", +// "subset": "v3" +// }}] +// }] +// } +// } +// ` +// res, err := ParseParam(str) +// if err != nil { +// t.Errorf("ParseParam() => got %v", err) +// } else { +// fmt.Println(res) +// } +//} + +//func TestUpdateK8sAddress(t *testing.T) { +// cases := []struct { +// path, addr, want, err string +// }{ +// { +// path: "k8s.yml222", +// addr: "filenoteexisttest", +// want: "", +// err: "*os.PathError", +// }, +// { +// path: configPath, +// addr: "", +// want: "", +// err: "", +// }, +// { +// path: configPath, +// addr: "k8stest", +// want: "k8stest", +// err: "", +// }, +// } + +// oldEnv := os.Getenv(models.EnvK8sAddress) +// for _, cas := range cases { +// os.Unsetenv(models.EnvK8sAddress) +// os.Setenv(models.EnvK8sAddress, cas.addr) + +// got, err := updateK8sAddress(cas.path) +// if got != cas.want || (err != nil && reflect.TypeOf(err).String() != cas.err) { +// t.Errorf("updateK8sAddress(%s, %s) => got %s %v, want %s", cas.path, cas.addr, got, reflect.TypeOf(err), cas.want) +// } +// } +// os.Setenv(models.EnvK8sAddress, oldEnv) +//} diff --git a/msb2pilot/src/msb2pilot/pilot/msb.go b/msb2pilot/src/msb2pilot/pilot/msb.go index fb87d08..875715e 100644 --- a/msb2pilot/src/msb2pilot/pilot/msb.go +++ b/msb2pilot/src/msb2pilot/pilot/msb.go @@ -17,8 +17,6 @@ import ( "msb2pilot/models" "msb2pilot/msb" "os" - "regexp" - "strings" istioModel "istio.io/istio/pilot/pkg/model" ) @@ -28,64 +26,59 @@ var ( ) const ( - routerulePrefix = "msbcustom." + defaultVirtualService = "default-apigateway" ) func SyncMsbData(newServices []*models.MsbService) { - if len(cachedServices) == 0 { - deleteAllMsbRules() - } log.Log.Debug("sync msb rewrite rule to pilot") - createServices, updateServices, deleteServices := compareServices(cachedServices, newServices) - - saveService(OperationCreate, createServices) - saveService(OperationUpdate, updateServices) - saveService(OperationDelete, deleteServices) - cachedServices = newServices -} - -func saveService(operation Operation, services []*models.MsbService) { - if len(services) == 0 { - log.Log.Debug("0 services need to %s. \n", operation) + serviceUpdated := isUpdated(cachedServices, newServices) + if !serviceUpdated { // no service updated return } - configs, err := parseServiceToConfig(services) - if err != nil { - log.Log.Error("param parse error", err) - return - } - fails := Save(operation, configs) - log.Log.Debug("%d services %d rules need to %s, %d fails. \n", len(services), len(configs), operation, len(fails)) -} + log.Log.Debug("service updated") -func deleteAllMsbRules() { - log.Log.Informational("delete all msb rules") - configs, err := List("routerules", "") + apiGateway := os.Getenv(models.EnvApiGatewayName) + publishServices := getPublishServiceMap() + virtueServiceString := parseServiceToConfig(apiGateway, newServices, publishServices) + log.Log.Debug(virtueServiceString) + configs, err := ParseParam(virtueServiceString) if err != nil { - log.Log.Error("fail to load rule list", err) + log.Log.Error("param parse error", err) return } - deleteList := msbRuleFilter(configs) - failed := Save(OperationDelete, deleteList) - log.Log.Debug("deleteAllMsbRules total %d rules, fail %d", len(configs), len(failed)) + updateVirtualService(newServices, configs) } -func msbRuleFilter(configs []istioModel.Config) []istioModel.Config { - res := make([]istioModel.Config, 0, len(configs)) - - for _, config := range configs { - if strings.HasPrefix(config.Name, routerulePrefix) { - res = append(res, config) +func updateVirtualService(newServices []*models.MsbService, configs []istioModel.Config) { + // if virtualservice exist, then delete it + config, exist := Get("virtualservice", "default", defaultVirtualService) + if exist { + log.Log.Informational("default virtual is: %v", config) + err := Delete("virtualservice", "default", defaultVirtualService) + if err != nil { + log.Log.Debug("failed to delete virture service %v \n", err) + return } } - return res + if len(newServices) == 0 { + cachedServices = newServices + return + } + + fails := Save(OperationCreate, configs) + if len(fails) != 0 { + log.Log.Debug("failed to create virture service %v \n", fails) + return + } else { + cachedServices = newServices + } } -func compareServices(oldServices, newServices []*models.MsbService) (createServices, updateServices, deleteServices []*models.MsbService) { +func isUpdated(oldServices, newServices []*models.MsbService) bool { oldServiceMap := toServiceMap(oldServices) newServiceMap := toServiceMap(newServices) @@ -93,21 +86,22 @@ func compareServices(oldServices, newServices []*models.MsbService) (createServi if oldService, exist := oldServiceMap[key]; exist { // service exist: check whether need to update if oldService.ModifyIndex != newService.ModifyIndex { - updateServices = append(updateServices, newService) + // service updated + return true } } else { - // service not exist: add - createServices = append(createServices, newService) + // old service not exist: add + return true } delete(oldServiceMap, key) } - for _, service := range oldServiceMap { - deleteServices = append(deleteServices, service) + if len(oldServiceMap) != 0 { // some service has been deleted + return true } - return + return false } func toServiceMap(services []*models.MsbService) map[string]*models.MsbService { @@ -120,20 +114,39 @@ func toServiceMap(services []*models.MsbService) map[string]*models.MsbService { return serviceMap } -func parseServiceToConfig(services []*models.MsbService) ([]istioModel.Config, error) { - publishServices := getPublishServiceMap() - apiGateway := os.Getenv(models.EnvApiGatewayName) +func parseServiceToConfig(host string, services []*models.MsbService, publishServices map[string]*models.PublishService) string { + httpRoutes := getAllHttpRoute(services, publishServices) + + rule := `{ +"apiVersion": "networking.istio.io/v1alpha3", +"kind": "VirtualService", +"metadata": {"name": "` + defaultVirtualService + `"}, +"spec": {"hosts":["` + host + `"],"http":[` + httpRoutes + `]} +}` + + return rule +} + +func getAllHttpRoute(services []*models.MsbService, publishServices map[string]*models.PublishService) string { var buf bytes.Buffer + hasPre := false for _, service := range services { if publishService, exist := publishServices[getPublishServiceKey(service)]; exist { if service.ConsulLabels.BaseInfo != nil { - rule := createRouteRule(apiGateway, publishService.PublishUrl, service.ServiceName, service.ConsulLabels.BaseInfo.Url) + if hasPre { + buf.WriteString(",") + } + + rule := createHttpRoute(publishService.PublishUrl, service.ServiceName, service.ConsulLabels.BaseInfo.Url) buf.WriteString(rule) + + hasPre = true } } } - return ParseParam(buf.String()) + + return buf.String() } func getPublishServiceKey(svc *models.MsbService) string { @@ -163,51 +176,118 @@ func getPublishServiceMap() map[string]*models.PublishService { return res } -func createRouteRule(sourceService, sourcePath, targetService, targetPath string) string { +//func createRouteRule(sourceService, sourcePath, targetService, targetPath string) string { +// if sourcePath == "" { +// sourcePath = "/" +// } +// if targetPath == "" { +// targetPath = "/" +// } +// // rule name must consist of lower case alphanuberic charactoers, '-' or '.'. and must start and end with an alphanumberic charactore +// r := regexp.MustCompile("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*") +// strs := r.FindAllString(targetService, -1) +// name := routerulePrefix + strings.Join(strs, "") +// name = strings.ToLower(name) + +// rule := `{ +//"apiVersion": "config.istio.io/v1alpha2", +//"kind": "RouteRule", +//"metadata": { +// "name": "` + name + `" +//}, +//"spec": { +// "destination":{ +// "name":"` + sourceService + `" +// }, +// "match":{ +// "request":{ +// "headers": { +// "uri": { +// "prefix": "` + sourcePath + `" +// } +// } +// } +// }, +// "rewrite": { +// "uri": "` + targetPath + `" +// }, +// "route":[ +// { +// "destination":{ +// "name":"` + targetService + `" +// } +// } +// ] +//} +//} + +//` +// return rule +//} + +//func createRouteRule(sourceService, sourcePath, targetService, targetPath string) string { +// if sourcePath == "" { +// sourcePath = "/" +// } +// if targetPath == "" { +// targetPath = "/" +// } + +// rule := ` +//apiVersion: networking.istio.io/v1alpha3 +//kind: VirtualService +//metadata: +// name: default-apigateway +//spec: +// hosts: +// - reviews +// http: +// - match: +// - headers: +// end-user: +// exact: jason +// route: +// - destination: +// host: reviews +// - route: +// - destination: +// host: reviews +// ` +// return rule +//} + +func createHttpRoute(sourcePath, targetHost, targetPath string) string { + // - match: + // - uri: + // prefix: /ratings + // rewrite: + // uri: /v1/bookRatings + // route: + // - destination: + // host: ratings.prod.svc.cluster.local + // subset: v1 + if sourcePath == "" { sourcePath = "/" } if targetPath == "" { targetPath = "/" } - // rule name must consist of lower case alphanuberic charactoers, '-' or '.'. and must start and end with an alphanumberic charactore - r := regexp.MustCompile("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*") - strs := r.FindAllString(targetService, -1) - name := routerulePrefix + strings.Join(strs, "") - name = strings.ToLower(name) - rule := `{ -"apiVersion": "config.istio.io/v1alpha2", -"kind": "RouteRule", -"metadata": { - "name": "` + name + `" -}, -"spec": { - "destination":{ - "name":"` + sourceService + `" - }, - "match":{ - "request":{ - "headers": { - "uri": { - "prefix": "` + sourcePath + `" - } - } - } - }, - "rewrite": { - "uri": "` + targetPath + `" - }, - "route":[ - { - "destination":{ - "name":"` + targetService + `" - } - } - ] -} + httpRoute := `{ +"match":[{"uri": {"prefix": "` + sourcePath + `"}}], +"rewrite": {"uri": "` + targetPath + `"}, +"route": [` + createDestinationWeight(targetHost) + `] +}` + + return httpRoute } -` - return rule +func createDestinationWeight(targetHost string) string { + // destination: + // host: reviews.prod.svc.cluster.local + // subset: v2 + // weight: 25 + + return `{"destination": {"host": "` + targetHost + `"}}` } diff --git a/msb2pilot/src/msb2pilot/pilot/msb_test.go b/msb2pilot/src/msb2pilot/pilot/msb_test.go index e3cf7ad..ca5e84f 100644 --- a/msb2pilot/src/msb2pilot/pilot/msb_test.go +++ b/msb2pilot/src/msb2pilot/pilot/msb_test.go @@ -12,97 +12,112 @@ package pilot import ( + "msb2pilot/models" "testing" ) -func TestCreateRouteRule(t *testing.T) { +func TestParseServiceToConfig(t *testing.T) { cases := []struct { - sService, sPath, tService, tPath, want string + services []*models.MsbService + publishServices map[string]*models.PublishService + want string }{ - { // success demo - sService: "sservice", - sPath: "/", - tService: "tservice", - tPath: "/", + { + services: []*models.MsbService{}, + publishServices: map[string]*models.PublishService{}, want: `{ -"apiVersion": "config.istio.io/v1alpha2", -"kind": "RouteRule", -"metadata": { - "name": "msbcustom.tservice" -}, -"spec": { - "destination":{ - "name":"sservice" - }, - "match":{ - "request":{ - "headers": { - "uri": { - "prefix": "/" - } - } - } - }, - "rewrite": { - "uri": "/" - }, - "route":[ - { - "destination":{ - "name":"tservice" - } - } - ] -} -} - -`, +"apiVersion": "networking.istio.io/v1alpha3", +"kind": "VirtualService", +"metadata": {"name": "default-apigateway"}, +"spec": {"hosts":["tService"],"http":[]} +}`, }, - { // rule name must consist of lower case alphanuberic charactoers, '-' or '.'. and must start and end with an alphanumberic charactore - sService: "sservice", - sPath: "/", - tService: "123ABCrule-name.test~!@#$%^&*()_+321", - tPath: "/", + { + services: []*models.MsbService{ + &models.MsbService{ + ConsulLabels: &models.ConsulLabels{ + NameSpace: &models.NameSpace{ + NameSpace: "service1namespace", + }, + BaseInfo: &models.BaseInfo{ + Version: "service1v1", + Url: "service1url", + }, + }, + ServiceName: "service1", + }, + &models.MsbService{ + ConsulLabels: &models.ConsulLabels{ + NameSpace: &models.NameSpace{ + NameSpace: "service2namespace", + }, + BaseInfo: &models.BaseInfo{ + Version: "service2v2", + Url: "service2url", + }, + }, + ServiceName: "service2", + }, + }, + publishServices: map[string]*models.PublishService{ + "service1service1v1service1namespace": &models.PublishService{ + ServiceName: "service1", + Version: "service1v1", + NameSpace: "service1namespace", + PublishUrl: "service1publishurl", + }, + "service2service2v2service2namespace": &models.PublishService{ + ServiceName: "service2", + Version: "service2v2", + NameSpace: "service2namespace", + PublishUrl: "service2publihurl", + }, + }, want: `{ -"apiVersion": "config.istio.io/v1alpha2", -"kind": "RouteRule", -"metadata": { - "name": "msbcustom.123rule-name.test321" -}, -"spec": { - "destination":{ - "name":"sservice" - }, - "match":{ - "request":{ - "headers": { - "uri": { - "prefix": "/" - } - } - } - }, - "rewrite": { - "uri": "/" - }, - "route":[ - { - "destination":{ - "name":"123ABCrule-name.test~!@#$%^&*()_+321" - } - } - ] -} -} - -`, +"apiVersion": "networking.istio.io/v1alpha3", +"kind": "VirtualService", +"metadata": {"name": "default-apigateway"}, +"spec": {"hosts":["tService"],"http":[{ +"match":{"uri": {"prefix": "service1publishurl"}}, +"rewrite": {"uri": "service1url"}, +"route": [{"destination": {"host": "service1"}}] +},{ +"match":{"uri": {"prefix": "service2publihurl"}}, +"rewrite": {"uri": "service2url"}, +"route": [{"destination": {"host": "service2"}}] +}]} +}`, }, } for _, cas := range cases { - got := createRouteRule(cas.sService, cas.sPath, cas.tService, cas.tPath) + got := parseServiceToConfig("tService", cas.services, cas.publishServices) if got != cas.want { - t.Errorf("createRouteRule(%s, %s, %s, %s) => got %s, want %s", cas.sService, cas.sPath, cas.tService, cas.tPath, got, cas.want) + t.Errorf("parseServiceToConfig() => got %s, want %s", got, cas.want) } } } + +//func TestCreateHttpRoute(t *testing.T) { +// cases := []struct { +// sPath, tService, tPath, want string +// }{ +// { // success demo +// sPath: "/", +// tService: "tService", +// tPath: "/", +// want: `{ +//"match":{"uri": {"prefix": "/"}}, +//"rewrite": {"uri": "/"}, +//"route": [{"destination": {"host": "tService"}}] +//}`, +// }, +// } + +// for _, cas := range cases { +// got := createHttpRoute(cas.sPath, cas.tService, cas.tPath) +// if got != cas.want { +// t.Errorf("createHttpRoute(%s, %s, %s) => got %s, want %s", cas.sPath, cas.tService, cas.tPath, got, cas.want) +// } +// } +//} |