200 lines
7.4 KiB
Go
200 lines
7.4 KiB
Go
//
|
|
// Copyright (c) 2019-2020 Red Hat, Inc.
|
|
// This program and the accompanying materials are made
|
|
// available under the terms of the Eclipse Public License 2.0
|
|
// which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
//
|
|
// SPDX-License-Identifier: EPL-2.0
|
|
//
|
|
// Contributors:
|
|
// Red Hat, Inc. - initial API and implementation
|
|
//
|
|
|
|
package solver
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/devfile/devworkspace-operator/pkg/constants"
|
|
|
|
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
|
|
"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers"
|
|
"github.com/eclipse-che/che-operator/api/v2alpha1"
|
|
controller "github.com/eclipse-che/che-operator/controllers/devworkspace"
|
|
"github.com/eclipse-che/che-operator/controllers/devworkspace/defaults"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
|
)
|
|
|
|
var (
|
|
logger = ctrl.Log.WithName("solver")
|
|
)
|
|
|
|
// CheRoutingSolver is a struct representing the routing solver for Che specific routing of devworkspaces
|
|
type CheRoutingSolver struct {
|
|
client client.Client
|
|
scheme *runtime.Scheme
|
|
}
|
|
|
|
// Magic to ensure we get compile time error right here if our struct doesn't support the interface.
|
|
var _ solvers.RoutingSolverGetter = (*CheRouterGetter)(nil)
|
|
var _ solvers.RoutingSolver = (*CheRoutingSolver)(nil)
|
|
|
|
// CheRouterGetter negotiates the solver with the calling code
|
|
type CheRouterGetter struct {
|
|
scheme *runtime.Scheme
|
|
}
|
|
|
|
// Getter creates a new CheRouterGetter
|
|
func Getter(scheme *runtime.Scheme) *CheRouterGetter {
|
|
return &CheRouterGetter{
|
|
scheme: scheme,
|
|
}
|
|
}
|
|
|
|
func (g *CheRouterGetter) HasSolver(routingClass controllerv1alpha1.DevWorkspaceRoutingClass) bool {
|
|
return isSupported(routingClass)
|
|
}
|
|
|
|
func (g *CheRouterGetter) GetSolver(client client.Client, routingClass controllerv1alpha1.DevWorkspaceRoutingClass) (solver solvers.RoutingSolver, err error) {
|
|
if !isSupported(routingClass) {
|
|
return nil, solvers.RoutingNotSupported
|
|
}
|
|
return &CheRoutingSolver{client: client, scheme: g.scheme}, nil
|
|
}
|
|
|
|
func (g *CheRouterGetter) SetupControllerManager(mgr *builder.Builder) error {
|
|
|
|
// We want to watch configmaps and re-map the reconcile on the devworkspace routing, if possible
|
|
// This way we can react on changes of the gateway configmap changes by re-reconciling the corresponding
|
|
// devworkspace routing and thus keeping the devworkspace routing in a functional state
|
|
// TODO is this going to be performant enough in a big cluster with very many configmaps?
|
|
mgr.Watches(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(func(mo handler.MapObject) []reconcile.Request {
|
|
applicable, key := isGatewayWorkspaceConfig(mo.Meta)
|
|
|
|
if applicable {
|
|
// cool, we can trigger the reconcile of the routing so that we can update the configmap that has just changed under our hands
|
|
return []reconcile.Request{
|
|
{
|
|
NamespacedName: key,
|
|
},
|
|
}
|
|
} else {
|
|
return []reconcile.Request{}
|
|
}
|
|
})})
|
|
|
|
return nil
|
|
}
|
|
|
|
func isGatewayWorkspaceConfig(obj metav1.Object) (bool, types.NamespacedName) {
|
|
workspaceID := obj.GetLabels()[constants.DevWorkspaceIDLabel]
|
|
objectName := obj.GetName()
|
|
|
|
// bail out quickly if we're not dealing with a configmap with an expected name
|
|
if objectName != defaults.GetGatewayWorkpaceConfigMapName(workspaceID) {
|
|
return false, types.NamespacedName{}
|
|
}
|
|
|
|
routingName := obj.GetAnnotations()[defaults.ConfigAnnotationDevWorkspaceRoutingName]
|
|
routingNamespace := obj.GetAnnotations()[defaults.ConfigAnnotationDevWorkspaceRoutingNamespace]
|
|
|
|
// if there is no annotation for the routing, we're out of luck.. this should not happen though
|
|
if routingName == "" {
|
|
return false, types.NamespacedName{}
|
|
}
|
|
|
|
// cool, we found a configmap belonging to a concrete devworkspace routing
|
|
return true, types.NamespacedName{Name: routingName, Namespace: routingNamespace}
|
|
}
|
|
|
|
func (c *CheRoutingSolver) FinalizerRequired(routing *controllerv1alpha1.DevWorkspaceRouting) bool {
|
|
return true
|
|
}
|
|
|
|
func (c *CheRoutingSolver) Finalize(routing *controllerv1alpha1.DevWorkspaceRouting) error {
|
|
cheManager, err := cheManagerOfRouting(routing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.cheRoutingFinalize(cheManager, routing)
|
|
}
|
|
|
|
// GetSpecObjects constructs cluster routing objects which should be applied on the cluster
|
|
func (c *CheRoutingSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRouting, workspaceMeta solvers.DevWorkspaceMetadata) (solvers.RoutingObjects, error) {
|
|
cheManager, err := cheManagerOfRouting(routing)
|
|
if err != nil {
|
|
return solvers.RoutingObjects{}, err
|
|
}
|
|
|
|
return c.cheSpecObjects(cheManager, routing, workspaceMeta)
|
|
}
|
|
|
|
// GetExposedEndpoints retreives the URL for each endpoint in a devfile spec from a set of RoutingObjects.
|
|
// Returns is a map from component ids (as defined in the devfile) to the list of endpoints for that component
|
|
// Return value "ready" specifies if all endpoints are resolved on the cluster; if false it is necessary to retry, as
|
|
// URLs will be undefined.
|
|
func (c *CheRoutingSolver) GetExposedEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, routingObj solvers.RoutingObjects) (exposedEndpoints map[string]controllerv1alpha1.ExposedEndpointList, ready bool, err error) {
|
|
if len(routingObj.Services) == 0 {
|
|
return map[string]controllerv1alpha1.ExposedEndpointList{}, true, nil
|
|
}
|
|
|
|
managerName := routingObj.Services[0].Annotations[defaults.ConfigAnnotationCheManagerName]
|
|
managerNamespace := routingObj.Services[0].Annotations[defaults.ConfigAnnotationCheManagerNamespace]
|
|
workspaceID := routingObj.Services[0].Labels[constants.DevWorkspaceIDLabel]
|
|
|
|
manager, err := findCheManager(client.ObjectKey{Name: managerName, Namespace: managerNamespace})
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return c.cheExposedEndpoints(manager, workspaceID, endpoints, routingObj)
|
|
}
|
|
|
|
func isSupported(routingClass controllerv1alpha1.DevWorkspaceRoutingClass) bool {
|
|
return routingClass == "che"
|
|
}
|
|
|
|
func cheManagerOfRouting(routing *controllerv1alpha1.DevWorkspaceRouting) (*v2alpha1.CheCluster, error) {
|
|
cheName := routing.Annotations[defaults.ConfigAnnotationCheManagerName]
|
|
cheNamespace := routing.Annotations[defaults.ConfigAnnotationCheManagerNamespace]
|
|
|
|
return findCheManager(client.ObjectKey{Name: cheName, Namespace: cheNamespace})
|
|
}
|
|
|
|
func findCheManager(cheManagerKey client.ObjectKey) (*v2alpha1.CheCluster, error) {
|
|
managers := controller.GetCurrentCheClusterInstances()
|
|
if len(managers) == 0 {
|
|
// the CheManager has not been reconciled yet, so let's wait a bit
|
|
return &v2alpha1.CheCluster{}, &solvers.RoutingNotReady{Retry: 1 * time.Second}
|
|
}
|
|
|
|
if len(cheManagerKey.Name) == 0 {
|
|
if len(managers) > 1 {
|
|
return &v2alpha1.CheCluster{}, &solvers.RoutingInvalid{Reason: fmt.Sprintf("the routing does not specify any Che manager in its configuration but there are %d Che managers in the cluster", len(managers))}
|
|
}
|
|
for _, m := range managers {
|
|
return &m, nil
|
|
}
|
|
|
|
}
|
|
|
|
if m, ok := managers[cheManagerKey]; ok {
|
|
return &m, nil
|
|
}
|
|
|
|
logger.Info("Routing requires a non-existing che manager. Retrying in 10 seconds.", "key", cheManagerKey)
|
|
|
|
return &v2alpha1.CheCluster{}, &solvers.RoutingNotReady{Retry: 10 * time.Second}
|
|
}
|