diff --git a/proxy/ha_proxy.go b/proxy/ha_proxy.go index b7086d4f..b79086b8 100644 --- a/proxy/ha_proxy.go +++ b/proxy/ha_proxy.go @@ -176,14 +176,51 @@ 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 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 another service or group.") + return + } + + 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 { + 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 + } } -// 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..c702ecae 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,56 @@ func (s *HaProxyTestSuite) Test_AddService_AddsService() { 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"} + 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) +} + +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() { +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 +2399,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 a45b7dcd..10a8a4ef 100644 --- a/proxy/types.go +++ b/proxy/types.go @@ -145,6 +145,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.