From eb9b4578e179c3699a762ce92c7e8e5bdd6aaa2a Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Sun, 25 Feb 2018 00:21:01 +0200 Subject: [PATCH 1/8] issue-434 - WIP --- proxy/ha_proxy.go | 39 +++++++++++++++++++++++++++++++++++---- proxy/proxy.go | 1 + proxy/types.go | 2 ++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index b7086d4f..76e2e58f 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -51,6 +51,7 @@ type configData struct { // NewHaProxy returns an instance of the proxy func NewHaProxy(templatesPath, configsPath string) proxy { dataInstance.Services = map[string]Service{} + dataInstance.Groups = map[string][]Service{} return HaProxy{ templatesPath: templatesPath, configsPath: configsPath, @@ -176,14 +177,44 @@ func (m HaProxy) Reload() error { } // AddService puts a service into `dataInstance` map. -// The key of the map is `ServiceName` +// The key of the map is `ServiceName` if `ServiceGroup` is not specified; otherwise `ServiceGroup`. func (m HaProxy) AddService(service Service) { - dataInstance.Services[service.ServiceName] = service + if len(service.ServiceGroup) > 0 { + dataInstance.Groups[service.ServiceGroup] = append(dataInstance.Groups[service.ServiceGroup], service) + + if _, found := dataInstance.Services[service.ServiceGroup]; !found { + dataInstance.Services[service.ServiceGroup] = service + } + } else { + dataInstance.Services[service.ServiceName] = service + } } -// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key +// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key. If there is no service +// with this name, the given parameter it will be used as `ServiceGroup`. func (m HaProxy) RemoveService(service string) { - delete(dataInstance.Services, service) + if _, found := dataInstance.Services[service]; found { + delete(dataInstance.Services, service) + return + } + + for group, services := range dataInstance.Groups { + for idx, srv := range services { + if srv.ServiceName == service { + list := append(services[:idx], services[idx+1:]...) + + if len(list) == 0 { + delete(dataInstance.Services, group) + delete(dataInstance.Groups, group) + } else { + dataInstance.Services[group] = list[0] + dataInstance.Groups[group] = list + } + + return + } + } + } } // GetServices returns a map with all the services used by the proxy. diff --git a/proxy/proxy.go b/proxy/proxy.go index 717f258f..1e4bc72e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,6 +5,7 @@ var proxyInstance proxy = HaProxy{} // Data contains the information about all the services type Data struct { Services map[string]Service + Groups map[string][]Service } var dataInstance = Data{} diff --git a/proxy/types.go b/proxy/types.go index 3ef218fc..1f4f43cc 100644 --- a/proxy/types.go +++ b/proxy/types.go @@ -143,6 +143,8 @@ type Service struct { // The name of the service. // It must match the name of the Swarm service. ServiceName string `split_words:"true"` + // Allows multiple services to be recognized as a single service. If any of them is removed, the remaining ones will ensure the response. + ServiceGroup string `split_words:"true"` // Determines the type of sticky sessions. If set to `sticky-server`, session cookie will be set by the proxy. Any other value means that sticky sessions are not used and load balancing is performed by Docker's Overlay network. Please open an issue if you'd like support for other types of sticky sessions. SessionType string `split_words:"true"` // Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one. From 956c613994b58544522cf0a25fec25898d97b292 Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Sun, 25 Feb 2018 18:46:30 +0200 Subject: [PATCH 2/8] Revert "Reverted #435" This reverts commit f9955dd535344ee22fa1b54752c98010f0085fd9. --- proxy/ha_proxy_test.go | 35 ----------------------------------- proxy/template.go | 4 ++-- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index 8e7d208e..6d6b0a9d 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -1527,41 +1527,6 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed s.Equal(expectedData, actualData) } -func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_usesHttpsRedirectCode() { - var actualData string - tmpl := s.TemplateContent - expectedData := fmt.Sprintf( - `%s - acl url_my-service1111_0 path_beg /path - acl domain_my-service1111_0 hdr_beg(host) -i my-domain.com - use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0 - acl is_my-service_http hdr(X-Forwarded-Proto) http - http-request redirect scheme https code 301 if is_my-service_http url_my-service1111_0 domain_my-service1111_0 - acl is_my-service_https hdr(X-Forwarded-Proto) https - http-request redirect scheme https code 301 if !is_my-service_https url_my-service1111_0 domain_my-service1111_0%s`, - tmpl, - s.ServicesContent, - ) - writeFile = func(filename string, data []byte, perm os.FileMode) error { - actualData = string(data) - return nil - } - p := NewHaProxy(s.TemplatesPath, s.ConfigsPath) - dataInstance.Services["my-service"] = Service{ - ServiceName: "my-service", - PathType: "path_beg", - RedirectWhenHttpProto: true, - AclName: "my-service", - ServiceDest: []ServiceDest{ - {Port: "1111", ServicePath: []string{"/path"}, ServiceDomain: []string{"my-domain.com"}, HttpsRedirectCode: "301"}, - }, - } - - p.CreateConfigFromTemplates() - - s.Equal(expectedData, actualData) -} - func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRedirectFromDomainIsSet() { var actualData string tmpl := s.TemplateContent diff --git a/proxy/template.go b/proxy/template.go index f6324928..687eb3ab 100644 --- a/proxy/template.go +++ b/proxy/template.go @@ -64,9 +64,9 @@ func getFrontTemplate(s Service) string { {{- if eq .ReqMode "http"}} {{- if ne .Port ""}} acl is_{{$.AclName}}_http hdr(X-Forwarded-Proto) http - http-request redirect scheme https{{if .HttpsRedirectCode}} code {{.HttpsRedirectCode}}{{end}} if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} + http-request redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} acl is_{{$.AclName}}_https hdr(X-Forwarded-Proto) https - http-request redirect scheme https{{if .HttpsRedirectCode}} code {{.HttpsRedirectCode}}{{end}} if !is_{{$.AclName}}_https url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} + http-request redirect scheme https if !is_{{$.AclName}}_https url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} {{- end}} {{- end}} {{- end}} From ec651be7224fa7ec23ba30e9f9f6f486d797264e Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 07:54:41 +0200 Subject: [PATCH 3/8] Added support for service grouping - added GroupedServiceNames field - added more unit tests for AddService and RemoveService --- proxy/ha_proxy.go | 35 +++++++++++++------------------- proxy/ha_proxy_test.go | 45 ++++++++++++++++++++++++++++++++++++++++-- proxy/proxy.go | 1 - proxy/types.go | 2 ++ 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index 76e2e58f..a1e0b2ee 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -51,7 +51,6 @@ type configData struct { // NewHaProxy returns an instance of the proxy func NewHaProxy(templatesPath, configsPath string) proxy { dataInstance.Services = map[string]Service{} - dataInstance.Groups = map[string][]Service{} return HaProxy{ templatesPath: templatesPath, configsPath: configsPath, @@ -180,9 +179,10 @@ func (m HaProxy) Reload() error { // The key of the map is `ServiceName` if `ServiceGroup` is not specified; otherwise `ServiceGroup`. func (m HaProxy) AddService(service Service) { if len(service.ServiceGroup) > 0 { - dataInstance.Groups[service.ServiceGroup] = append(dataInstance.Groups[service.ServiceGroup], service) - - if _, found := dataInstance.Services[service.ServiceGroup]; !found { + if _, found := dataInstance.Services[service.ServiceGroup]; found { + dataInstance.Services[service.ServiceGroup].GroupedServiceNames[service.ServiceName] = true + } else { + service.GroupedServiceNames = map[string]bool{service.ServiceName: true} dataInstance.Services[service.ServiceGroup] = service } } else { @@ -192,23 +192,16 @@ func (m HaProxy) AddService(service Service) { // RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key. If there is no service // with this name, the given parameter it will be used as `ServiceGroup`. -func (m HaProxy) RemoveService(service string) { - if _, found := dataInstance.Services[service]; found { - delete(dataInstance.Services, service) - return - } - - for group, services := range dataInstance.Groups { - for idx, srv := range services { - if srv.ServiceName == service { - list := append(services[:idx], services[idx+1:]...) - - if len(list) == 0 { - delete(dataInstance.Services, group) - delete(dataInstance.Groups, group) - } else { - dataInstance.Services[group] = list[0] - dataInstance.Groups[group] = list +func (m HaProxy) RemoveService(serviceName string) { + if _, found := dataInstance.Services[serviceName]; found { + delete(dataInstance.Services, serviceName) + } else { + for groupName, service := range dataInstance.Services { + if _, found := service.GroupedServiceNames[serviceName]; found { + delete(service.GroupedServiceNames, serviceName) + + if len(service.GroupedServiceNames) == 0 { + delete(dataInstance.Services, groupName) } return diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index 6d6b0a9d..cf511e1d 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -2291,7 +2291,7 @@ func (s *HaProxyTestSuite) Test_RunCmd_AddsExtraArguments() { // AddService -func (s *HaProxyTestSuite) Test_AddService_AddsService() { +func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenNoGroups() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2303,9 +2303,23 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService() { s.Equal(dataInstance.Services[s1.ServiceName], s1) } +func (s *HaProxyTestSuite) Test_AddService_AddsService_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + + s.Len(dataInstance.Services, 1) + + s.Equal(dataInstance.Services[s1.ServiceGroup].ServiceName, s1.ServiceName) + s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 2) +} + // RemoveService -func (s *HaProxyTestSuite) Test_AddService_RemovesService() { +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_WhenNoGroups() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2317,6 +2331,33 @@ func (s *HaProxyTestSuite) Test_AddService_RemovesService() { s.Len(dataInstance.Services, 1) } +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + p.RemoveService(s1.ServiceName) + + s.Len(dataInstance.Services, 1) + s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 1) + + p.RemoveService(s2.ServiceName) + s.Len(dataInstance.Services, 0) +} + +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByGroupName_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + p.RemoveService(s1.ServiceGroup) + + s.Len(dataInstance.Services, 0) +} // Util func (s *HaProxyTestSuite) getTemplateWithLogs() string { diff --git a/proxy/proxy.go b/proxy/proxy.go index 1e4bc72e..717f258f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,7 +5,6 @@ var proxyInstance proxy = HaProxy{} // Data contains the information about all the services type Data struct { Services map[string]Service - Groups map[string][]Service } var dataInstance = Data{} diff --git a/proxy/types.go b/proxy/types.go index 1f4f43cc..f7cbc447 100644 --- a/proxy/types.go +++ b/proxy/types.go @@ -145,6 +145,8 @@ type Service struct { ServiceName string `split_words:"true"` // Allows multiple services to be recognized as a single service. If any of them is removed, the remaining ones will ensure the response. ServiceGroup string `split_words:"true"` + // Stores the names of the services that are behind a ServiceGroup. + GroupedServiceNames map[string]bool // Determines the type of sticky sessions. If set to `sticky-server`, session cookie will be set by the proxy. Any other value means that sticky sessions are not used and load balancing is performed by Docker's Overlay network. Please open an issue if you'd like support for other types of sticky sessions. SessionType string `split_words:"true"` // Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one. From 236118883e1f6c0bc54c49b12ebed99bb9646f69 Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 08:12:58 +0200 Subject: [PATCH 4/8] Revert to f9955dd --- proxy/ha_proxy.go | 34 +++--------------- proxy/ha_proxy_test.go | 80 +++++++++++++++++++----------------------- proxy/template.go | 4 +-- proxy/types.go | 4 --- 4 files changed, 44 insertions(+), 78 deletions(-) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index a1e0b2ee..b7086d4f 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -176,38 +176,14 @@ func (m HaProxy) Reload() error { } // AddService puts a service into `dataInstance` map. -// The key of the map is `ServiceName` if `ServiceGroup` is not specified; otherwise `ServiceGroup`. +// The key of the map is `ServiceName` func (m HaProxy) AddService(service Service) { - if len(service.ServiceGroup) > 0 { - if _, found := dataInstance.Services[service.ServiceGroup]; found { - dataInstance.Services[service.ServiceGroup].GroupedServiceNames[service.ServiceName] = true - } else { - service.GroupedServiceNames = map[string]bool{service.ServiceName: true} - dataInstance.Services[service.ServiceGroup] = service - } - } else { - dataInstance.Services[service.ServiceName] = service - } + dataInstance.Services[service.ServiceName] = service } -// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key. If there is no service -// with this name, the given parameter it will be used as `ServiceGroup`. -func (m HaProxy) RemoveService(serviceName string) { - if _, found := dataInstance.Services[serviceName]; found { - delete(dataInstance.Services, serviceName) - } else { - for groupName, service := range dataInstance.Services { - if _, found := service.GroupedServiceNames[serviceName]; found { - delete(service.GroupedServiceNames, serviceName) - - if len(service.GroupedServiceNames) == 0 { - delete(dataInstance.Services, groupName) - } - - return - } - } - } +// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key +func (m HaProxy) RemoveService(service string) { + delete(dataInstance.Services, service) } // GetServices returns a map with all the services used by the proxy. diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index cf511e1d..8e7d208e 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -1527,6 +1527,41 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed s.Equal(expectedData, actualData) } +func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_usesHttpsRedirectCode() { + var actualData string + tmpl := s.TemplateContent + expectedData := fmt.Sprintf( + `%s + acl url_my-service1111_0 path_beg /path + acl domain_my-service1111_0 hdr_beg(host) -i my-domain.com + use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0 + acl is_my-service_http hdr(X-Forwarded-Proto) http + http-request redirect scheme https code 301 if is_my-service_http url_my-service1111_0 domain_my-service1111_0 + acl is_my-service_https hdr(X-Forwarded-Proto) https + http-request redirect scheme https code 301 if !is_my-service_https url_my-service1111_0 domain_my-service1111_0%s`, + tmpl, + s.ServicesContent, + ) + writeFile = func(filename string, data []byte, perm os.FileMode) error { + actualData = string(data) + return nil + } + p := NewHaProxy(s.TemplatesPath, s.ConfigsPath) + dataInstance.Services["my-service"] = Service{ + ServiceName: "my-service", + PathType: "path_beg", + RedirectWhenHttpProto: true, + AclName: "my-service", + ServiceDest: []ServiceDest{ + {Port: "1111", ServicePath: []string{"/path"}, ServiceDomain: []string{"my-domain.com"}, HttpsRedirectCode: "301"}, + }, + } + + p.CreateConfigFromTemplates() + + s.Equal(expectedData, actualData) +} + func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRedirectFromDomainIsSet() { var actualData string tmpl := s.TemplateContent @@ -2291,7 +2326,7 @@ func (s *HaProxyTestSuite) Test_RunCmd_AddsExtraArguments() { // AddService -func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenNoGroups() { +func (s *HaProxyTestSuite) Test_AddService_AddsService() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2303,23 +2338,9 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenNoGroups() { s.Equal(dataInstance.Services[s1.ServiceName], s1) } -func (s *HaProxyTestSuite) Test_AddService_AddsService_ForOneGroupWithTwoServices() { - s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} - s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} - p := NewHaProxy("anything", "doesn't").(HaProxy) - - p.AddService(s1) - p.AddService(s2) - - s.Len(dataInstance.Services, 1) - - s.Equal(dataInstance.Services[s1.ServiceGroup].ServiceName, s1.ServiceName) - s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 2) -} - // RemoveService -func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_WhenNoGroups() { +func (s *HaProxyTestSuite) Test_AddService_RemovesService() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2331,33 +2352,6 @@ func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_WhenNoGr s.Len(dataInstance.Services, 1) } -func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_ForOneGroupWithTwoServices() { - s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} - s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} - p := NewHaProxy("anything", "doesn't").(HaProxy) - - p.AddService(s1) - p.AddService(s2) - p.RemoveService(s1.ServiceName) - - s.Len(dataInstance.Services, 1) - s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 1) - - p.RemoveService(s2.ServiceName) - s.Len(dataInstance.Services, 0) -} - -func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByGroupName_ForOneGroupWithTwoServices() { - s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} - s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} - p := NewHaProxy("anything", "doesn't").(HaProxy) - - p.AddService(s1) - p.AddService(s2) - p.RemoveService(s1.ServiceGroup) - - s.Len(dataInstance.Services, 0) -} // Util func (s *HaProxyTestSuite) getTemplateWithLogs() string { diff --git a/proxy/template.go b/proxy/template.go index 687eb3ab..f6324928 100644 --- a/proxy/template.go +++ b/proxy/template.go @@ -64,9 +64,9 @@ func getFrontTemplate(s Service) string { {{- if eq .ReqMode "http"}} {{- if ne .Port ""}} acl is_{{$.AclName}}_http hdr(X-Forwarded-Proto) http - http-request redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} + http-request redirect scheme https{{if .HttpsRedirectCode}} code {{.HttpsRedirectCode}}{{end}} if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} acl is_{{$.AclName}}_https hdr(X-Forwarded-Proto) https - http-request redirect scheme https if !is_{{$.AclName}}_https url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} + http-request redirect scheme https{{if .HttpsRedirectCode}} code {{.HttpsRedirectCode}}{{end}} if !is_{{$.AclName}}_https url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}} {{- end}} {{- end}} {{- end}} diff --git a/proxy/types.go b/proxy/types.go index f7cbc447..3ef218fc 100644 --- a/proxy/types.go +++ b/proxy/types.go @@ -143,10 +143,6 @@ type Service struct { // The name of the service. // It must match the name of the Swarm service. ServiceName string `split_words:"true"` - // Allows multiple services to be recognized as a single service. If any of them is removed, the remaining ones will ensure the response. - ServiceGroup string `split_words:"true"` - // Stores the names of the services that are behind a ServiceGroup. - GroupedServiceNames map[string]bool // Determines the type of sticky sessions. If set to `sticky-server`, session cookie will be set by the proxy. Any other value means that sticky sessions are not used and load balancing is performed by Docker's Overlay network. Please open an issue if you'd like support for other types of sticky sessions. SessionType string `split_words:"true"` // Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one. From ea7ec39420d62d6647770a83faf8745fb6dff08c Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 08:25:44 +0200 Subject: [PATCH 5/8] Added support for service grouping - added GroupedServiceNames field - added more unit tests for AddService and RemoveService --- proxy/ha_proxy.go | 34 ++++++++++++++++++++++++++----- proxy/ha_proxy_test.go | 45 ++++++++++++++++++++++++++++++++++++++++-- proxy/types.go | 4 ++++ 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index b7086d4f..a1e0b2ee 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -176,14 +176,38 @@ func (m HaProxy) Reload() error { } // AddService puts a service into `dataInstance` map. -// The key of the map is `ServiceName` +// The key of the map is `ServiceName` if `ServiceGroup` is not specified; otherwise `ServiceGroup`. func (m HaProxy) AddService(service Service) { - dataInstance.Services[service.ServiceName] = service + if len(service.ServiceGroup) > 0 { + if _, found := dataInstance.Services[service.ServiceGroup]; found { + dataInstance.Services[service.ServiceGroup].GroupedServiceNames[service.ServiceName] = true + } else { + service.GroupedServiceNames = map[string]bool{service.ServiceName: true} + dataInstance.Services[service.ServiceGroup] = service + } + } else { + dataInstance.Services[service.ServiceName] = service + } } -// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key -func (m HaProxy) RemoveService(service string) { - delete(dataInstance.Services, service) +// RemoveService deletes a service from the `dataInstance` map using `ServiceName` as the key. If there is no service +// with this name, the given parameter it will be used as `ServiceGroup`. +func (m HaProxy) RemoveService(serviceName string) { + if _, found := dataInstance.Services[serviceName]; found { + delete(dataInstance.Services, serviceName) + } else { + for groupName, service := range dataInstance.Services { + if _, found := service.GroupedServiceNames[serviceName]; found { + delete(service.GroupedServiceNames, serviceName) + + if len(service.GroupedServiceNames) == 0 { + delete(dataInstance.Services, groupName) + } + + return + } + } + } } // GetServices returns a map with all the services used by the proxy. diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index 8e7d208e..a7504605 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -2326,7 +2326,7 @@ func (s *HaProxyTestSuite) Test_RunCmd_AddsExtraArguments() { // AddService -func (s *HaProxyTestSuite) Test_AddService_AddsService() { +func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenNoGroups() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2338,9 +2338,23 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService() { s.Equal(dataInstance.Services[s1.ServiceName], s1) } +func (s *HaProxyTestSuite) Test_AddService_AddsService_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + + s.Len(dataInstance.Services, 1) + + s.Equal(dataInstance.Services[s1.ServiceGroup].ServiceName, s1.ServiceName) + s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 2) +} + // RemoveService -func (s *HaProxyTestSuite) Test_AddService_RemovesService() { +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_WhenNoGroups() { s1 := Service{ServiceName: "my-service-1"} s2 := Service{ServiceName: "my-service-2"} p := NewHaProxy("anything", "doesn't").(HaProxy) @@ -2352,6 +2366,33 @@ func (s *HaProxyTestSuite) Test_AddService_RemovesService() { s.Len(dataInstance.Services, 1) } +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + p.RemoveService(s1.ServiceName) + + s.Len(dataInstance.Services, 1) + s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 1) + + p.RemoveService(s2.ServiceName) + s.Len(dataInstance.Services, 0) +} + +func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByGroupName_ForOneGroupWithTwoServices() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + p.RemoveService(s1.ServiceGroup) + + s.Len(dataInstance.Services, 0) +} // Util func (s *HaProxyTestSuite) getTemplateWithLogs() string { diff --git a/proxy/types.go b/proxy/types.go index 3ef218fc..f7cbc447 100644 --- a/proxy/types.go +++ b/proxy/types.go @@ -143,6 +143,10 @@ type Service struct { // The name of the service. // It must match the name of the Swarm service. ServiceName string `split_words:"true"` + // Allows multiple services to be recognized as a single service. If any of them is removed, the remaining ones will ensure the response. + ServiceGroup string `split_words:"true"` + // Stores the names of the services that are behind a ServiceGroup. + GroupedServiceNames map[string]bool // Determines the type of sticky sessions. If set to `sticky-server`, session cookie will be set by the proxy. Any other value means that sticky sessions are not used and load balancing is performed by Docker's Overlay network. Please open an issue if you'd like support for other types of sticky sessions. SessionType string `split_words:"true"` // Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one. From c053839a5782e2b687ea8a2d84c1f335113873dc Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 13:33:00 +0200 Subject: [PATCH 6/8] added some checks when adding a service --- proxy/ha_proxy.go | 8 ++++++++ proxy/ha_proxy_test.go | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index a1e0b2ee..2cbf4f65 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -179,6 +179,14 @@ func (m HaProxy) Reload() error { // The key of the map is `ServiceName` if `ServiceGroup` is not specified; otherwise `ServiceGroup`. func (m HaProxy) AddService(service Service) { if len(service.ServiceGroup) > 0 { + if service.ServiceName == service.ServiceGroup { + logPrintf("The service name cannot be the same as the service group. On remove, it will remove the group.") + return + } else if _, found := dataInstance.Services[service.ServiceName]; found { + logPrintf("The service name is already used by a service group.") + return + } + if _, found := dataInstance.Services[service.ServiceGroup]; found { dataInstance.Services[service.ServiceGroup].GroupedServiceNames[service.ServiceName] = true } else { diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index a7504605..8cedc222 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -2352,6 +2352,27 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService_ForOneGroupWithTwoService s.Len(dataInstance.Services[s1.ServiceGroup].GroupedServiceNames, 2) } +func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenServiceNameMatchesTheGroupName() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "my-service-1"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + s.Len(dataInstance.Services, 0) +} + +func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenServiceNameMatchesOtherGroupName() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "my-service"} + s2 := Service{ServiceName: "my-service-2", ServiceGroup: "my-service"} + s3 := Service{ServiceName: "my-service"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + p.AddService(s3) + + s.Len(dataInstance.Services, 1) +} + // RemoveService func (s *HaProxyTestSuite) Test_AddService_RemovesService_ByServiceName_WhenNoGroups() { From ee4a27579a435711398f8fb43b955114d9497849 Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 13:44:40 +0200 Subject: [PATCH 7/8] added some checks when adding a service --- proxy/ha_proxy.go | 5 +++++ proxy/ha_proxy_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index 2cbf4f65..0f7a52f6 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -194,6 +194,11 @@ func (m HaProxy) AddService(service Service) { dataInstance.Services[service.ServiceGroup] = service } } else { + if _, found := dataInstance.Services[service.ServiceName]; found { + logPrintf("The service name is already used by another service or group.") + return + } + dataInstance.Services[service.ServiceName] = service } } diff --git a/proxy/ha_proxy_test.go b/proxy/ha_proxy_test.go index 8cedc222..c702ecae 100644 --- a/proxy/ha_proxy_test.go +++ b/proxy/ha_proxy_test.go @@ -2338,6 +2338,18 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenNoGroups() { s.Equal(dataInstance.Services[s1.ServiceName], s1) } +func (s *HaProxyTestSuite) Test_AddService_AddsService_WhenServiceNameMatchesAnotherGroup() { + s1 := Service{ServiceName: "my-service-1", ServiceGroup: "my-service"} + s2 := Service{ServiceName: "my-service"} + p := NewHaProxy("anything", "doesn't").(HaProxy) + + p.AddService(s1) + p.AddService(s2) + + s.Len(dataInstance.Services, 1) + s.Equal(dataInstance.Services[s1.ServiceGroup].ServiceName, s1.ServiceName) +} + func (s *HaProxyTestSuite) Test_AddService_AddsService_ForOneGroupWithTwoServices() { s1 := Service{ServiceName: "my-service-1", ServiceGroup: "group-1"} s2 := Service{ServiceName: "my-service-2", ServiceGroup: "group-1"} From 652c0242ced4a106849570963fa9d37ab598c0fd Mon Sep 17 00:00:00 2001 From: Mihai CAZACU Date: Tue, 27 Feb 2018 13:47:13 +0200 Subject: [PATCH 8/8] fixed a log message --- proxy/ha_proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index 0f7a52f6..b79086b8 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -183,7 +183,7 @@ func (m HaProxy) AddService(service Service) { logPrintf("The service name cannot be the same as the service group. On remove, it will remove the group.") return } else if _, found := dataInstance.Services[service.ServiceName]; found { - logPrintf("The service name is already used by a service group.") + logPrintf("The service name is already used by another service or group.") return }