Skip to content
47 changes: 42 additions & 5 deletions proxy/ha_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
78 changes: 76 additions & 2 deletions proxy/ha_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions proxy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down