Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ func (r *DevWorkspaceRoutingReconciler) Reconcile(ctx context.Context, req ctrl.
return reconcile.Result{}, r.markRoutingFailed(instance, "DevWorkspaceRouting requires field routingClass to be set")
}

solver, err := r.SolverGetter.GetSolver(r.Client, instance.Spec.RoutingClass)
solver, err := r.SolverGetter.GetSolver(r.Client, reqLogger, instance.Spec.RoutingClass)
if err != nil {
if errors.Is(err, solvers.RoutingNotSupported) {
reqLogger.Info("Routing class not supported by this controller, skipping reconciliation", "routingClass", instance.Spec.RoutingClass)
return reconcile.Result{}, nil
}
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Invalid routingClass for DevWorkspace: %s", err))
Expand Down Expand Up @@ -149,6 +150,12 @@ func (r *DevWorkspaceRoutingReconciler) Reconcile(ctx context.Context, req ctrl.
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Unable to provision networking for DevWorkspace: %s", invalid))
}

var conflict *solvers.ServiceConflictError
if errors.As(err, &conflict) {
reqLogger.Error(conflict, "Routing controller detected a service conflict", "serviceName", conflict.Reason)
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Unable to provision networking for DevWorkspace: %s", conflict))
}

// generic error, just fail the reconciliation
return reconcile.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var routeAnnotations = func(endpointName string, endpointAnnotations map[string]string) map[string]string {
Expand Down Expand Up @@ -47,10 +49,21 @@ var nginxIngressAnnotations = func(endpointName string, endpointAnnotations map[
// According to the current cluster there is different behavior:
// Kubernetes: use Ingresses without TLS
// OpenShift: use Routes with TLS enabled
type BasicSolver struct{}
type BasicSolver struct {
client client.Client
logger logr.Logger
}

var _ RoutingSolver = (*BasicSolver)(nil)

// NewBasicSolver creates a new BasicSolver with the provided dependencies
func NewBasicSolver(client client.Client, logger logr.Logger) *BasicSolver {
return &BasicSolver{
client: client,
logger: logger,
}
}

func (s *BasicSolver) FinalizerRequired(*controllerv1alpha1.DevWorkspaceRouting) bool {
return false
}
Expand All @@ -70,7 +83,11 @@ func (s *BasicSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRou

spec := routing.Spec
services := getServicesForEndpoints(spec.Endpoints, workspaceMeta)
services = append(services, GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta)...)
discoverableServices, err := GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta, s.client, s.logger)
if err != nil {
return RoutingObjects{}, err
}
services = append(services, discoverableServices...)
routingObjects.Services = services
if infrastructure.IsOpenShift() {
routingObjects.Routes = getRoutesForSpec(routingSuffix, spec.Endpoints, workspaceMeta)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,31 @@ import (
corev1 "k8s.io/api/core/v1"

controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
serviceServingCertAnnot = "service.beta.openshift.io/serving-cert-secret-name"
)

type ClusterSolver struct {
TLS bool
TLS bool
client client.Client
logger logr.Logger
}

var _ RoutingSolver = (*ClusterSolver)(nil)

// NewClusterSolver creates a new ClusterSolver with the provided dependencies
func NewClusterSolver(client client.Client, logger logr.Logger, tls bool) *ClusterSolver {
return &ClusterSolver{
TLS: tls,
client: client,
logger: logger,
}
}

func (s *ClusterSolver) FinalizerRequired(*controllerv1alpha1.DevWorkspaceRouting) bool {
return false
}
Expand All @@ -47,6 +60,11 @@ func (s *ClusterSolver) Finalize(*controllerv1alpha1.DevWorkspaceRouting) error
func (s *ClusterSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRouting, workspaceMeta DevWorkspaceMetadata) (RoutingObjects, error) {
spec := routing.Spec
services := getServicesForEndpoints(spec.Endpoints, workspaceMeta)
discoverableServices, err := GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta, s.client, s.logger)
if err != nil {
return RoutingObjects{}, err
}
services = append(services, discoverableServices...)
podAdditions := &controllerv1alpha1.PodAdditions{}
if s.TLS {
readOnlyMode := int32(420)
Expand Down
38 changes: 31 additions & 7 deletions controllers/controller/devworkspacerouting/solvers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
package solvers

import (
"context"
"fmt"

controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"

routeV1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -36,7 +42,7 @@ type DevWorkspaceMetadata struct {

// GetDiscoverableServicesForEndpoints converts the endpoint list into a set of services, each corresponding to a single discoverable
// endpoint from the list. Endpoints with the NoneEndpointExposure are ignored.
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata) []corev1.Service {
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata, cl client.Client, log logr.Logger) ([]corev1.Service, error) {
var services []corev1.Service
for _, machineEndpoints := range endpoints {
for _, endpoint := range machineEndpoints {
Expand All @@ -45,18 +51,36 @@ func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1
}

if endpoint.Attributes.GetBoolean(string(controllerv1alpha1.DiscoverableAttribute), nil) {
// Create service with name matching endpoint
// TODO: This could cause a reconcile conflict if multiple workspaces define the same discoverable endpoint
// Also endpoint names may not be valid as service names
serviceName := common.EndpointName(endpoint.Name)
log.Info("Checking for existing service for discoverable endpoint", "serviceName", serviceName)
existingService := &corev1.Service{}
err := cl.Get(context.TODO(), client.ObjectKey{Name: serviceName, Namespace: meta.Namespace}, existingService)
if err != nil {
if !errors.IsNotFound(err) {
log.Error(err, "Failed to get service from cluster", "serviceName", serviceName)
return nil, err
}
log.Info("No existing service found", "serviceName", serviceName)
} else {
log.Info("Found existing service", "serviceName", serviceName)
if existingService.Labels[constants.DevWorkspaceIDLabel] != meta.DevWorkspaceId {
log.Info("Service conflict detected", "serviceName", serviceName, "existingWorkspaceId", existingService.Labels[constants.DevWorkspaceIDLabel], "currentWorkspaceId", meta.DevWorkspaceId)
return nil, &ServiceConflictError{
Reason: fmt.Sprintf("discoverable endpoint %s conflicts with existing service", endpoint.Name),
}
}
log.Info("Existing service is owned by the same workspace", "serviceName", serviceName)
}

servicePort := corev1.ServicePort{
Name: common.EndpointName(endpoint.Name),
Name: serviceName,
Protocol: corev1.ProtocolTCP,
Port: int32(endpoint.TargetPort),
TargetPort: intstr.FromInt(endpoint.TargetPort),
}
services = append(services, corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: common.EndpointName(endpoint.Name),
Name: serviceName,
Namespace: meta.Namespace,
Labels: map[string]string{
constants.DevWorkspaceIDLabel: meta.DevWorkspaceId,
Expand All @@ -74,7 +98,7 @@ func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1
}
}
}
return services
return services, nil
}

// GetServiceForEndpoints returns a single service that exposes all endpoints of given exposure types, possibly also including the discoverable types.
Expand Down
Loading
Loading