Repository: containous/maesh
Pull request #251 (2019-09-24 18:12:31)

Move to stateless Kubernetes Provider
This PR:

- modifies the Kubernetes provider to be stateless.
- reduces the number of kubernetes api calls by selecting objects first, then looping through them in-memory
- moves base config building to the provider, from the controller.

Partially solves #242. 

dtomcej: Build is running into issues due to the fact that the k8s provider tries to pull all data before the mesh services have a chance to be built.

This causes discrepancies due to the TCP state table not being populated until the mesh service is created.
dtomcej: We also need to move the Mesh service creation to also be stateless.

CRUD operations need to be atomic as well, so that they can be processed before the provider returns the atomic config.

Patch:
diff --git a/internal/controller/controller.go b/internal/controller/controller.go
index e3d3433..be9deaf 100644
--- a/internal/controller/controller.go
+++ b/internal/controller/controller.go
@@ -175,12 +175,18 @@ func (c *Controller) Run(stopCh <-chan struct{}) error {
 		}
 	}
 
-	// Load the state from the TCP State Configmap before running
+	// Load the state from the TCP State Configmap before running.
 	c.tcpStateTable, err = c.loadTCPStateTable()
 	if err != nil {
 		log.Errorf("encountered error loading TCP state table: %v", err)
 	}
 
+	// Create the mesh services here to ensure that they exist
+	log.Info("Creating initial mesh services")
+	if err := c.createMeshServices(); err != nil {
+		log.Errorf("could not create mesh services: %v", err)
+	}
+
 	// run the deployer to deploy configurations
 	go c.deployer.Run(stopCh)
 
@@ -195,22 +201,15 @@ func (c *Controller) Run(stopCh <-chan struct{}) error {
 
 // runWorker executes the loop to process new items added to the queue
 func (c *Controller) runWorker() {
-	log.Debug("MeshController.runWorker: starting")
-
 	// invoke processNextMessage to fetch and consume the next
 	// message put in the queue
 	for c.processNextMessage() {
-		log.Debugf("MeshController.runWorker: processing next item")
 	}
-
-	log.Debugf("MeshController.runWorker: completed")
 }
 
 // processNextConfiguration retrieves each queued item and takes the
 // necessary handler action.
 func (c *Controller) processNextMessage() bool {
-	log.Debugf("MeshController Waiting for next item to process...")
-
 	// fetch the next item (blocking) from the queue to process or
 	// if a shutdown is requested then return out of this to stop
 	// processing
@@ -243,11 +242,21 @@ func (c *Controller) processNextMessage() bool {
 }
 
 func (c *Controller) buildConfigurationFromProviders(event message.Message) {
+
+	// Create all mesh services
+	if err := c.createMeshServices(); err != nil {
+		log.Errorf("could not create mesh services: %v", err)
+	}
+
 	if c.smiEnabled {
 		c.smiProvider.BuildConfiguration(event, c.traefikConfig)
 		return
 	}
-	c.kubernetesProvider.BuildConfiguration(event, c.traefikConfig)
+	config, err := c.kubernetesProvider.BuildConfig()
+	if err != nil {
+		log.Errorf("unable to build configuration: %v", err)
+	}
+	c.traefikConfig = config
 }
 
 func (c *Controller) processCreatedMessage(event message.Message) {
@@ -258,17 +267,7 @@ func (c *Controller) processCreatedMessage(event message.Message) {
 			return
 		}
 
-		log.Debugf("MeshController ObjectCreated with type: *corev1.Service: %s/%s", obj.Namespace, obj.Name)
-
-		log.Debugf("Creating associated mesh service for service: %s/%s", obj.Namespace, obj.Name)
-
-		if _, err := c.createMeshService(obj); err != nil {
-			log.Errorf("Could not create mesh service: %v", err)
-			return
-		}
-
 	case *corev1.Endpoints:
-		log.Debugf("MeshController ObjectCreated with type: *corev1.Endpoints: %s/%s, skipping...", obj.Namespace, obj.Name)
 		return
 
 	case *corev1.Pod:
@@ -353,7 +352,6 @@ func (c *Controller) processDeletedMessage(event message.Message) {
 		log.Debugf("MeshController ObjectDeleted with type: *corev1.Endpoints: %s/%s", obj.Namespace, obj.Name)
 
 	case *corev1.Pod:
-		log.Debugf("MeshController ObjectDeleted with type: *corev1.Pod: %s/%s, skipping...", obj.Namespace, obj.Name)
 		return
 	}
 
@@ -362,11 +360,38 @@ func (c *Controller) processDeletedMessage(event message.Message) {
 
 }
 
-func (c *Controller) createMeshService(service *corev1.Service) (*corev1.Service, error) {
+func (c *Controller) createMeshServices() error {
+	services, err := c.clients.GetServices(metav1.NamespaceAll)
+	if err != nil {
+		return fmt.Errorf("unable to get services: %v", err)
+	}
+
+	log.Debugf("Found Services: %v", services)
+	for _, service := range services {
+		if c.ignored.Ignored(service.Name, service.Namespace) {
+			continue
+		}
+		meshServiceName := c.userServiceToMeshServiceName(service.Name, service.Namespace)
+		for _, subservice := range services {
+			// If there is already a mesh service created, don't bother recreating
+			if subservice.Name == meshServiceName && subservice.Namespace == c.meshNamespace {
+				continue
+			}
+		}
+		log.Infof("Creating associated mesh service: %s", meshServiceName)
+		if err := c.createMeshService(service); err != nil {
+			return fmt.Errorf("unable to get create mesh service: %v", err)
+		}
+	}
+	return nil
+}
+
+func (c *Controller) createMeshService(service *corev1.Service) error {
 	meshServiceName := c.userServiceToMeshServiceName(service.Name, service.Namespace)
-	meshServiceInstance, exists, err := c.clients.GetService(c.meshNamespace, meshServiceName)
+	log.Debugf("Creating mesh service: %s", meshServiceName)
+	_, exists, err := c.clients.GetService(c.meshNamespace, meshServiceName)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	if !exists {
@@ -389,6 +414,11 @@ func (c *Controller) createMeshService(service *corev1.Service) (*corev1.Service
 				targetPort = intstr.FromInt(c.getTCPPortFromState(service.Name, service.Namespace, sp.Port))
 			}
 
+			if targetPort.IntVal == 0 {
+				log.Errorf("Could not get TCP Port for service: %s with service port: %v", service.Name, sp)
+				continue
+			}
+
 			meshPort := corev1.ServicePort{
 				Name:       sp.Name,
 				Port:       sp.Port,
@@ -411,10 +441,10 @@ func (c *Controller) createMeshService(service *corev1.Service) (*corev1.Service
 			},
 		}
 
-		return c.clients.CreateService(svc)
+		_, err = c.clients.CreateService(svc)
 	}
 
-	return meshServiceInstance, nil
+	return err
 }
 
 func (c *Controller) deleteMeshService(serviceName, serviceNamespace string) error {
diff --git a/internal/k8s/client.go b/internal/k8s/client.go
index 4adf791..4459d1f 100644
--- a/internal/k8s/client.go
+++ b/internal/k8s/client.go
@@ -65,6 +65,7 @@ type CoreV1Client interface {
 	DeleteService(namespace, name string) error
 
 	GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
+	GetEndpointses(namespace string) ([]*corev1.Endpoints, error)
 
 	GetPod(namespace, name string) (*corev1.Pod, bool, error)
 	ListPodWithOptions(namespace string, options metav1.ListOptions) (*corev1.PodList, error)
@@ -548,7 +549,8 @@ func (w *ClientWrapper) GetServices(namespace string) ([]*corev1.Service, error)
 		return result, err
 	}
 	for _, service := range list.Items {
-		result = append(result, &service)
+		item := service
+		result = append(result, &item)
 	}
 	return result, nil
 }
@@ -585,6 +587,20 @@ func (w *ClientWrapper) GetEndpoints(namespace, name string) (*corev1.Endpoints,
 	return endpoints, exists, err
 }
 
+// GetEndpointses retrieves the endpoints from the specified namespace.
+func (w *ClientWrapper) GetEndpointses(namespace string) ([]*corev1.Endpoints, error) {
+	var result []*corev1.Endpoints
+	list, err := w.KubeClient.CoreV1().Endpoints(namespace).List(metav1.ListOptions{})
+	if err != nil {
+		return result, err
+	}
+	for _, endpoints := range list.Items {
+		item := endpoints
+		result = append(result, &item)
+	}
+	return result, nil
+}
+
 // GetPod retrieves the pod from the specified namespace.
 func (w *ClientWrapper) GetPod(namespace, name string) (*corev1.Pod, bool, error) {
 	pod, err := w.KubeClient.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
@@ -612,7 +628,8 @@ func (w *ClientWrapper) GetNamespaces() ([]*corev1.Namespace, error) {
 		return result, err
 	}
 	for _, namespace := range list.Items {
-		result = append(result, &namespace)
+		item := namespace
+		result = append(result, &item)
 	}
 	return result, nil
 }
diff --git a/internal/k8s/client_mock.go b/internal/k8s/client_mock.go
index f4ecb1d..ed74bee 100644
--- a/internal/k8s/client_mock.go
+++ b/internal/k8s/client_mock.go
@@ -265,6 +265,15 @@ func (c *CoreV1ClientMock) GetEndpoints(namespace, name string) (*corev1.Endpoin
 	return nil, false, c.apiEndpointsError
 }
 
+// GetEndpointses returns mocked date for endpoints.
+func (c *CoreV1ClientMock) GetEndpointses(namespace string) ([]*corev1.Endpoints, error) {
+	if c.apiEndpointsError != nil {
+		return nil, c.apiEndpointsError
+	}
+
+	return c.endpoints, nil
+}
+
 // GetPod returns mocked data for pod.
 func (c *CoreV1ClientMock) GetPod(namespace, name string) (*corev1.Pod, bool, error) {
 	if c.apiPodError != nil {
diff --git a/internal/providers/kubernetes/fixtures/build_configuration_simple_http_middlewares.yaml b/internal/providers/kubernetes/fixtures/build_configuration_simple_http_middlewares.yaml
new file mode 100644
index 0000000..9c7fcbb
--- /dev/null
+++ b/internal/providers/kubernetes/fixtures/build_configuration_simple_http_middlewares.yaml
@@ -0,0 +1,36 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: foo
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: test
+  namespace: foo
+  annotations:
+    maesh.containo.us/retry-attempts: "2"
+spec:
+  clusterIP: 10.1.0.1
+  selector:
+    app: test
+  ports:
+  - protocol: TCP
+    port: 80
+    targetPort: 80
+---
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: test
+  namespace: foo
+subsets:
+- addresses:
+  - ip: 10.0.0.1
+  ports:
+  - port: 80
+- addresses:
+  - ip: 10.0.0.2
+  ports:
+  - port: 80
+
diff --git a/internal/providers/kubernetes/fixtures/build_configuration_simple_tcp.yaml b/internal/providers/kubernetes/fixtures/build_configuration_simple_tcp.yaml
new file mode 100644
index 0000000..988692d
--- /dev/null
+++ b/internal/providers/kubernetes/fixtures/build_configuration_simple_tcp.yaml
@@ -0,0 +1,36 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: foo
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: test
+  namespace: foo
+  annotations:
+    maesh.containo.us/traffic-type: tcp
+spec:
+  clusterIP: 10.1.0.1
+  selector:
+    app: test
+  ports:
+  - protocol: TCP
+    port: 80
+    targetPort: 80
+---
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: test
+  namespace: foo
+subsets:
+- addresses:
+  - ip: 10.0.0.1
+  ports:
+  - port: 80
+- addresses:
+  - ip: 10.0.0.2
+  ports:
+  - port: 80
+
diff --git a/internal/providers/kubernetes/provider.go b/internal/providers/kubernetes/provider.go
index e79b5f9..1647350 100644
--- a/internal/providers/kubernetes/provider.go
+++ b/internal/providers/kubernetes/provider.go
@@ -8,10 +8,10 @@ import (
 	"strconv"
 
 	"github.com/containous/maesh/internal/k8s"
-	"github.com/containous/maesh/internal/message"
 	"github.com/containous/traefik/v2/pkg/config/dynamic"
 	log "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // Provider holds a client to access the provider.
@@ -41,32 +41,6 @@ func New(client k8s.CoreV1Client, defaultMode string, meshNamespace string, tcpS
 	return p
 }
 
-// BuildConfiguration builds the configuration for routing
-// from a native kubernetes environment.
-func (p *Provider) BuildConfiguration(event message.Message, traefikConfig *dynamic.Configuration) {
-	switch obj := event.Object.(type) {
-	case *corev1.Service:
-		switch event.Action {
-		case message.TypeCreated:
-			p.buildServiceIntoConfig(obj, nil, traefikConfig)
-		case message.TypeUpdated:
-			//FIXME: We will need to delete the old references in the config, and create the new service.
-		case message.TypeDeleted:
-			p.deleteServiceFromConfig(obj, traefikConfig)
-		}
-	case *corev1.Endpoints:
-		switch event.Action {
-		case message.TypeCreated:
-			// We don't process created endpoint events, processing is done under service creation.
-		case message.TypeUpdated:
-			p.buildServiceIntoConfig(nil, obj, traefikConfig)
-		case message.TypeDeleted:
-			// We don't precess deleted endpoint events, processig is done under service deletion.
-		}
-	}
-
-}
-
 func (p *Provider) buildRouter(name, namespace, ip string, port int, serviceName string, addMiddlewares bool) *dynamic.Router {
 	if addMiddlewares {
 		return &dynamic.Router{
@@ -136,73 +110,46 @@ func (p *Provider) buildTCPService(endpoints *corev1.Endpoints) *dynamic.TCPServ
 	}
 }
 
-func (p *Provider) buildServiceIntoConfig(service *corev1.Service, endpoints *corev1.Endpoints, config *dynamic.Configuration) {
-	var exists bool
-	var err error
-	if service == nil {
-		service, exists, err = p.client.GetService(endpoints.Namespace, endpoints.Name)
-		if err != nil {
-			log.Errorf("Could not get service %s/%s: %v", endpoints.Namespace, endpoints.Name, err)
-			return
-		}
-		if !exists {
-			log.Errorf("endpoints for service %s/%s do not exist", endpoints.Namespace, endpoints.Name)
-			return
-		}
-
+// BuildConfig builds the configuration for routing
+// from a native kubernetes environment.
+func (p *Provider) BuildConfig() (*dynamic.Configuration, error) {
+	services, err := p.client.GetServices(metav1.NamespaceAll)
+	if err != nil {
+		return nil, fmt.Errorf("unable to get services: %v", err)
 	}
 
-	if endpoints == nil {
-		endpoints, exists, err = p.client.GetEndpoints(service.Namespace, service.Name)
-		if err != nil {
-			log.Errorf("Could not get endpoints for service %s/%s: %v", service.Namespace, service.Name, err)
-			return
-		}
-		if !exists {
-			log.Errorf("endpoints for service %s/%s do not exist", service.Namespace, service.Name)
-			return
-		}
+	endpoints, err := p.client.GetEndpointses(metav1.NamespaceAll)
+	if err != nil {
+		return nil, fmt.Errorf("unable to get endpoints: %v", err)
 	}
 
-	serviceMode := p.getServiceMode(service.Annotations)
+	config := createBaseConfigWithReadiness()
 
-	for id, sp := range service.Spec.Ports {
-		key := buildKey(service.Name, service.Namespace, sp.Port)
+	for _, service := range services {
+		serviceMode := p.getServiceMode(service.Annotations)
 
-		if serviceMode == k8s.ServiceTypeHTTP {
-			config.HTTP.Services[key] = p.buildService(endpoints)
-			middlewares := p.buildHTTPMiddlewares(service.Annotations)
-			if middlewares != nil {
-				config.HTTP.Routers[key] = p.buildRouter(service.Name, service.Namespace, service.Spec.ClusterIP, 5000+id, key, true)
-				config.HTTP.Middlewares[key] = middlewares
+		for id, sp := range service.Spec.Ports {
+			key := buildKey(service.Name, service.Namespace, sp.Port)
+
+			if serviceMode == k8s.ServiceTypeHTTP {
+				config.HTTP.Services[key] = p.buildService(getEndpointsFromList(service.Name, service.Namespace, endpoints))
+				middlewares := p.buildHTTPMiddlewares(service.Annotations)
+				if middlewares != nil {
+					config.HTTP.Routers[key] = p.buildRouter(service.Name, service.Namespace, service.Spec.ClusterIP, 5000+id, key, true)
+					config.HTTP.Middlewares[key] = middlewares
+					continue
+				}
+				config.HTTP.Routers[key] = p.buildRouter(service.Name, service.Namespace, service.Spec.ClusterIP, 5000+id, key, false)
 				continue
 			}
-			config.HTTP.Routers[key] = p.buildRouter(service.Name, service.Namespace, service.Spec.ClusterIP, 5000+id, key, false)
-			continue
-		}
-
-		meshPort := p.getMeshPort(service.Name, service.Namespace, sp.Port)
-		config.TCP.Routers[key] = p.buildTCPRouter(meshPort, key)
-		config.TCP.Services[key] = p.buildTCPService(endpoints)
-	}
-}
 
-func (p *Provider) deleteServiceFromConfig(service *corev1.Service, config *dynamic.Configuration) {
-	serviceMode := p.getServiceMode(service.Annotations)
-
-	for _, sp := range service.Spec.Ports {
-		key := buildKey(service.Name, service.Namespace, sp.Port)
-
-		if serviceMode == k8s.ServiceTypeHTTP {
-			delete(config.HTTP.Routers, key)
-			delete(config.HTTP.Services, key)
-			delete(config.HTTP.Middlewares, key)
-			continue
+			meshPort := p.getMeshPort(service.Name, service.Namespace, sp.Port)
+			config.TCP.Routers[key] = p.buildTCPRouter(meshPort, key)
+			config.TCP.Services[key] = p.buildTCPService(getEndpointsFromList(service.Name, service.Namespace, endpoints))
 		}
-
-		delete(config.TCP.Routers, key)
-		delete(config.TCP.Services, key)
 	}
+
+	return config, nil
 }
 
 func (p *Provider) getServiceMode(annotations map[string]string) string {
@@ -263,6 +210,45 @@ func (p *Provider) getMeshPort(serviceName, serviceNamespace string, servicePort
 	return 0
 }
 
+func getEndpointsFromList(name, namespace string, endpointList []*corev1.Endpoints) *corev1.Endpoints {
+	for _, endpoints := range endpointList {
+		if endpoints.Name == name && endpoints.Namespace == namespace {
+			return endpoints
+		}
+	}
+	return nil
+}
+
+func createBaseConfigWithReadiness() *dynamic.Configuration {
+	return &dynamic.Configuration{
+		HTTP: &dynamic.HTTPConfiguration{
+			Routers: map[string]*dynamic.Router{
+				"readiness": {
+					Rule:        "Path(`/ping`)",
+					EntryPoints: []string{"readiness"},
+					Service:     "readiness",
+				},
+			},
+			Services: map[string]*dynamic.Service{
+				"readiness": {
+					LoadBalancer: &dynamic.ServersLoadBalancer{
+						Servers: []dynamic.Server{
+							{
+								URL: "http://127.0.0.1:8080",
+							},
+						},
+					},
+				},
+			},
+			Middlewares: map[string]*dynamic.Middleware{},
+		},
+		TCP: &dynamic.TCPConfiguration{
+			Routers:  map[string]*dynamic.TCPRouter{},
+			Services: map[string]*dynamic.TCPService{},
+		},
+	}
+}
+
 func buildKey(name, namespace string, port int32) string {
 	// Use the hash of the servicename.namespace.port as the key
 	// So that we can update services based on their name


Test patch:
diff --git a/integration/smi_test.go b/integration/smi_test.go
index e552aa5..8cd7523 100644
--- a/integration/smi_test.go
+++ b/integration/smi_test.go
@@ -426,7 +426,7 @@ func (s *SMISuite) createResources(c *check.C, dirPath string) {
 }
 
 func (s *SMISuite) deleteResources(c *check.C, dirPath string, force bool) {
-	// Create the required objects from the smi directory
+	// Delete the required objects from the smi directory
 	args := []string{"delete", "-f", path.Join(s.dir, dirPath)}
 	if force {
 		args = append(args, "--force", "--grace-period=0")
diff --git a/internal/providers/kubernetes/provider_test.go b/internal/providers/kubernetes/provider_test.go
index 34b7cb9..0598d3d 100644
--- a/internal/providers/kubernetes/provider_test.go
+++ b/internal/providers/kubernetes/provider_test.go
@@ -4,7 +4,6 @@ import (
 	"testing"
 
 	"github.com/containous/maesh/internal/k8s"
-	"github.com/containous/maesh/internal/message"
 	"github.com/containous/traefik/v2/pkg/config/dynamic"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
@@ -72,51 +71,41 @@ func TestBuildConfiguration(t *testing.T) {
 	testCases := []struct {
 		desc           string
 		mockFile       string
-		event          message.Message
-		provided       *dynamic.Configuration
 		expected       *dynamic.Configuration
 		endpointsError bool
 		serviceError   bool
 	}{
 		{
-			desc:     "simple configuration build with empty event",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-		},
-		{
-			desc:     "simple configuration build with HTTP service event",
+			desc:     "simple configuration build with HTTP service",
 			mockFile: "build_configuration_simple.yaml",
 			expected: &dynamic.Configuration{
 				HTTP: &dynamic.HTTPConfiguration{
 					Routers: map[string]*dynamic.Router{
+						"readiness": {
+							EntryPoints: []string{"readiness"},
+							Service:     "readiness",
+							Rule:        "Path(`/ping`)",
+						},
 						"test-foo-80-6653beb49ee354ea": {
 							EntryPoints: []string{"http-5000"},
 							Service:     "test-foo-80-6653beb49ee354ea",
 							Rule:        "Host(`test.foo.maesh`) || Host(`10.1.0.1`)",
 						},
 					},
+					Middlewares: map[string]*dynamic.Middleware{},
 					Services: map[string]*dynamic.Service{
+						"readiness": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								PassHostHeader: false,
+								Servers: []dynamic.Server{
+									{
+										URL:    "http://127.0.0.1:8080",
+										Scheme: "",
+										Port:   "",
+									},
+								},
+							},
+						},
 						"test-foo-80-6653beb49ee354ea": {
 							LoadBalancer: &dynamic.ServersLoadBalancer{
 								PassHostHeader: true,
@@ -141,370 +130,45 @@ func TestBuildConfiguration(t *testing.T) {
 					Services: map[string]*dynamic.TCPService{},
 				},
 			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:  map[string]*dynamic.Router{},
-					Services: map[string]*dynamic.Service{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-					},
-					Spec: corev1.ServiceSpec{
-						ClusterIP: "10.1.0.1",
-						Ports: []corev1.ServicePort{
-							{
-								Name:     "test",
-								Port:     80,
-								Protocol: "TCP",
-							},
-						},
-					},
-				},
-				Action: message.TypeCreated,
-			},
 		},
 		{
-			desc:     "simple configuration build with TCP service event",
-			mockFile: "build_configuration_simple.yaml",
+			desc:     "simple configuration build with HTTP service middlewares",
+			mockFile: "build_configuration_simple_http_middlewares.yaml",
 			expected: &dynamic.Configuration{
 				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers: map[string]*dynamic.TCPRouter{
-						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"tcp-10000"},
-							Service:     "test-foo-80-6653beb49ee354ea",
-							Rule:        "HostSNI(`*`)",
-						},
-					},
-					Services: map[string]*dynamic.TCPService{
-						"test-foo-80-6653beb49ee354ea": {
-							LoadBalancer: &dynamic.TCPServersLoadBalancer{
-								Servers: []dynamic.TCPServer{
-									{
-										Address: "10.0.0.1:80",
-									},
-									{
-										Address: "10.0.0.2:80",
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-						Annotations: map[string]string{
-							k8s.AnnotationServiceType: k8s.ServiceTypeTCP,
-						},
-					},
-					Spec: corev1.ServiceSpec{
-						ClusterIP: "10.1.0.1",
-						Ports: []corev1.ServicePort{
-							{
-								Name:     "test",
-								Port:     80,
-								Protocol: "TCP",
-							},
+					Routers: map[string]*dynamic.Router{
+						"readiness": {
+							EntryPoints: []string{"readiness"},
+							Service:     "readiness",
+							Rule:        "Path(`/ping`)",
 						},
-					},
-				},
-				Action: message.TypeCreated,
-			},
-		},
-		{
-			desc:     "endpoints error",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-					},
-				},
-				Action: message.TypeCreated,
-			},
-
-			endpointsError: true,
-		},
-		{
-			desc:     "endpoints not exist error",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "bar",
-					},
-				},
-				Action: message.TypeCreated,
-			},
-		},
-		{
-			desc:     "service error",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Endpoints{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-					},
-				},
-				Action: message.TypeUpdated,
-			},
-
-			serviceError: true,
-		},
-		{
-			desc:     "service not exist error",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers:  map[string]*dynamic.TCPRouter{},
-					Services: map[string]*dynamic.TCPService{},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Endpoints{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "bar",
-					},
-				},
-				Action: message.TypeUpdated,
-			},
-		},
-		{
-			desc:     "simple configuration delete HTTP service event",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers:     map[string]*dynamic.Router{},
-					Services:    map[string]*dynamic.Service{},
-					Middlewares: map[string]*dynamic.Middleware{},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers: map[string]*dynamic.TCPRouter{
 						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"tcp-10000"},
+							EntryPoints: []string{"http-5000"},
+							Middlewares: []string{"test-foo-80-6653beb49ee354ea"},
 							Service:     "test-foo-80-6653beb49ee354ea",
-							Rule:        "HostSNI(`*`)",
+							Rule:        "Host(`test.foo.maesh`) || Host(`10.1.0.1`)",
 						},
 					},
-					Services: map[string]*dynamic.TCPService{
+					Middlewares: map[string]*dynamic.Middleware{
 						"test-foo-80-6653beb49ee354ea": {
-							LoadBalancer: &dynamic.TCPServersLoadBalancer{
-								Servers: []dynamic.TCPServer{
-									{
-										Address: "10.0.0.1:80",
-									},
-									{
-										Address: "10.0.0.2:80",
-									},
-								},
+							Retry: &dynamic.Retry{
+								Attempts: 2,
 							},
 						},
 					},
-				},
-			},
-			provided: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers: map[string]*dynamic.Router{
-						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"http-5000"},
-							Service:     "test-foo-80-6653beb49ee354ea",
-							Middlewares: []string{"test-foo-80-6653beb49ee354ea"},
-							Rule:        "Host(`test.foo.maesh`) || Host(`10.1.0.1`)",
-						},
-					},
 					Services: map[string]*dynamic.Service{
-						"test-foo-80-6653beb49ee354ea": {
+						"readiness": {
 							LoadBalancer: &dynamic.ServersLoadBalancer{
-								PassHostHeader: true,
+								PassHostHeader: false,
 								Servers: []dynamic.Server{
 									{
-										URL:    "http://10.0.0.1:80",
-										Scheme: "",
-										Port:   "",
-									},
-									{
-										URL:    "http://10.0.0.2:80",
+										URL:    "http://127.0.0.1:8080",
 										Scheme: "",
 										Port:   "",
 									},
 								},
 							},
 						},
-					},
-					Middlewares: map[string]*dynamic.Middleware{
-						"test-foo-80-6653beb49ee354ea": {},
-					},
-				},
-				TCP: &dynamic.TCPConfiguration{
-					Routers: map[string]*dynamic.TCPRouter{
-						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"tcp-10000"},
-							Service:     "test-foo-80-6653beb49ee354ea",
-							Rule:        "HostSNI(`*`)",
-						},
-					},
-					Services: map[string]*dynamic.TCPService{
-						"test-foo-80-6653beb49ee354ea": {
-							LoadBalancer: &dynamic.TCPServersLoadBalancer{
-								Servers: []dynamic.TCPServer{
-									{
-										Address: "10.0.0.1:80",
-									},
-									{
-										Address: "10.0.0.2:80",
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-					},
-					Spec: corev1.ServiceSpec{
-						ClusterIP: "10.1.0.1",
-						Ports: []corev1.ServicePort{
-							{
-								Name:     "test",
-								Port:     80,
-								Protocol: "TCP",
-							},
-						},
-					},
-				},
-				Action: message.TypeDeleted,
-			},
-		},
-		{
-			desc:     "simple configuration delete TCP service event",
-			mockFile: "build_configuration_simple.yaml",
-			expected: &dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Routers: map[string]*dynamic.Router{
-						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"http-5000"},
-							Service:     "test-foo-80-6653beb49ee354ea",
-							Rule:        "Host(`test.foo.maesh`) || Host(`10.1.0.1`)",
-						},
-					},
-					Services: map[string]*dynamic.Service{
 						"test-foo-80-6653beb49ee354ea": {
 							LoadBalancer: &dynamic.ServersLoadBalancer{
 								PassHostHeader: true,
@@ -529,27 +193,27 @@ func TestBuildConfiguration(t *testing.T) {
 					Services: map[string]*dynamic.TCPService{},
 				},
 			},
-			provided: &dynamic.Configuration{
+		},
+		{
+			desc:     "simple configuration build with TCP service",
+			mockFile: "build_configuration_simple_tcp.yaml",
+			expected: &dynamic.Configuration{
 				HTTP: &dynamic.HTTPConfiguration{
 					Routers: map[string]*dynamic.Router{
-						"test-foo-80-6653beb49ee354ea": {
-							EntryPoints: []string{"http-5000"},
-							Service:     "test-foo-80-6653beb49ee354ea",
-							Rule:        "Host(`test.foo.maesh`) || Host(`10.1.0.1`)",
+						"readiness": {
+							EntryPoints: []string{"readiness"},
+							Service:     "readiness",
+							Rule:        "Path(`/ping`)",
 						},
 					},
+					Middlewares: map[string]*dynamic.Middleware{},
 					Services: map[string]*dynamic.Service{
-						"test-foo-80-6653beb49ee354ea": {
+						"readiness": {
 							LoadBalancer: &dynamic.ServersLoadBalancer{
-								PassHostHeader: true,
+								PassHostHeader: false,
 								Servers: []dynamic.Server{
 									{
-										URL:    "http://10.0.0.1:80",
-										Scheme: "",
-										Port:   "",
-									},
-									{
-										URL:    "http://10.0.0.2:80",
+										URL:    "http://127.0.0.1:8080",
 										Scheme: "",
 										Port:   "",
 									},
@@ -582,28 +246,18 @@ func TestBuildConfiguration(t *testing.T) {
 					},
 				},
 			},
-			event: message.Message{
-				Object: &corev1.Service{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "foo",
-						Annotations: map[string]string{
-							k8s.AnnotationServiceType: k8s.ServiceTypeTCP,
-						},
-					},
-					Spec: corev1.ServiceSpec{
-						ClusterIP: "10.1.0.1",
-						Ports: []corev1.ServicePort{
-							{
-								Name:     "test",
-								Port:     80,
-								Protocol: "TCP",
-							},
-						},
-					},
-				},
-				Action: message.TypeDeleted,
-			},
+		},
+		{
+			desc:           "endpoints error",
+			mockFile:       "build_configuration_simple.yaml",
+			expected:       nil,
+			endpointsError: true,
+		},
+		{
+			desc:         "service error",
+			mockFile:     "build_configuration_simple.yaml",
+			expected:     nil,
+			serviceError: true,
 		},
 	}
 
@@ -621,8 +275,11 @@ func TestBuildConfiguration(t *testing.T) {
 			}
 
 			provider := New(clientMock, k8s.ServiceTypeHTTP, meshNamespace, stateTable)
-			provider.BuildConfiguration(test.event, test.provided)
-			assert.Equal(t, test.expected, test.provided)
+			config, err := provider.BuildConfig()
+			assert.Equal(t, test.expected, config)
+			if test.endpointsError || test.serviceError {
+				assert.Error(t, err)
+			}
 		})
 	}
 }
@@ -869,3 +526,77 @@ func TestBuildHTTPMiddlewares(t *testing.T) {
 		})
 	}
 }
+
+func TestGetEndpointsFromList(t *testing.T) {
+	testCases := []struct {
+		desc      string
+		provided  []*corev1.Endpoints
+		name      string
+		namespace string
+		expected  *corev1.Endpoints
+	}{
+		{
+			desc:      "empty list",
+			provided:  []*corev1.Endpoints{},
+			name:      "foo",
+			namespace: "bar",
+			expected:  nil,
+		},
+		{
+			desc: "match in list",
+			provided: []*corev1.Endpoints{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "foo",
+						Namespace: "bar",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "fifi",
+						Namespace: "fufu",
+					},
+				},
+			},
+			name:      "foo",
+			namespace: "bar",
+			expected: &corev1.Endpoints{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "bar",
+				},
+			},
+		},
+		{
+			desc: "no match in list",
+			provided: []*corev1.Endpoints{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "bar",
+						Namespace: "bar",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "fifi",
+						Namespace: "fufu",
+					},
+				},
+			},
+			name:      "foo",
+			namespace: "bar",
+			expected:  nil,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := getEndpointsFromList(test.name, test.namespace, test.provided)
+			assert.Equal(t, test.expected, actual)
+
+		})
+	}
+}