Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor sources endpoints #1181

Merged
merged 10 commits into from
May 21, 2024
133 changes: 84 additions & 49 deletions frontend/endpoints/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/odigos-io/odigos/common/consts"
"github.com/odigos-io/odigos/frontend/kube"
"golang.org/x/sync/errgroup"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -15,18 +15,18 @@ type GetApplicationsInNamespaceRequest struct {
}

type GetApplicationsInNamespaceResponse struct {
Applications []GetApplicationItem `json:"applications"`
Applications []GetApplicationItemInNamespace `json:"applications"`
}

type WorkloadKind string

const (
WorkloadKindDeployment WorkloadKind = "deployment"
WorkloadKindStatefulSet WorkloadKind = "statefulset"
WorkloadKindDaemonSet WorkloadKind = "daemonset"
WorkloadKindDeployment WorkloadKind = "Deployment"
WorkloadKindStatefulSet WorkloadKind = "StatefulSet"
WorkloadKindDaemonSet WorkloadKind = "DaemonSet"
)

type GetApplicationItem struct {
type GetApplicationItemInNamespace struct {
Name string `json:"name"`
Kind WorkloadKind `json:"kind"`
Instances int `json:"instances"`
Expand All @@ -35,6 +35,12 @@ type GetApplicationItem struct {
InstrumentationEffective bool `json:"instrumentation_effective"`
}

type GetApplicationItem struct {
// nameSpace is used when querying all the namespaces, the response can be grouped/filtered by namespace
nameSpace string
nsItem GetApplicationItemInNamespace
}

func GetApplicationsInNamespace(c *gin.Context) {
var request GetApplicationsInNamespaceRequest
if err := c.ShouldBindUri(&request); err != nil {
Expand All @@ -43,55 +49,80 @@ func GetApplicationsInNamespace(c *gin.Context) {
}

ctx := c.Request.Context()
deps, err := getDeployments(request.Namespace, ctx)
namespace, err := kube.DefaultClient.CoreV1().Namespaces().Get(ctx, request.Namespace, metav1.GetOptions{})
if err != nil {
returnError(c, err)
return
}

ss, err := getStatefulSets(request.Namespace, ctx)
items, err := getApplicationsInNamespace(ctx, namespace.Name, map[string]*bool{namespace.Name: isObjectLabeledForInstrumentation(namespace.ObjectMeta)})
if err != nil {
returnError(c, err)
return
}

dss, err := getDaemonSets(request.Namespace, ctx)
if err != nil {
returnError(c, err)
return
apps := make([]GetApplicationItemInNamespace, len(items))
for i, item := range items {
apps[i] = item.nsItem
}

c.JSON(http.StatusOK, GetApplicationsInNamespaceResponse{
Applications: apps,
})
}

// getApplicationsInNamespace returns all applications in the namespace and their instrumentation status.
// nsName can be an empty string to get applications in all namespaces.
// nsInstrumentedMap is a map of namespace name to a boolean pointer indicating if the namespace is instrumented.
func getApplicationsInNamespace(ctx context.Context, nsName string, nsInstrumentedMap map[string]*bool) ([]GetApplicationItem, error) {
g, ctx := errgroup.WithContext(ctx)
var (
deps []GetApplicationItem
ss []GetApplicationItem
dss []GetApplicationItem
)

g.Go(func() error {
var err error
deps, err = getDeployments(nsName, ctx)
return err
})

g.Go(func() error {
var err error
ss, err = getStatefulSets(nsName, ctx)
return err
})

g.Go(func() error {
var err error
dss, err = getDaemonSets(nsName, ctx)
return err
})

if err := g.Wait(); err != nil {
return nil, err
}

items := make([]GetApplicationItem, len(deps)+len(ss)+len(dss))
copy(items, deps)
copy(items[len(deps):], ss)
copy(items[len(deps)+len(ss):], dss)

// check if the entire namespace is instrumented
// as it affects the applications in the namespace
// which use this label to determine if they should be instrumented
namespace, err := kube.DefaultClient.CoreV1().Namespaces().Get(c.Request.Context(), request.Namespace, metav1.GetOptions{})
if err != nil {
returnError(c, err)
return
}
namespaceInstrumented, found := namespace.Labels[consts.OdigosInstrumentationLabel]
var nsInstrumentationLabeled *bool
if found {
instrumentationLabel := namespaceInstrumented == consts.InstrumentationEnabled
nsInstrumentationLabeled = &instrumentationLabel
}
for i := range items {
item := &items[i]
item.NsInstrumentationLabeled = nsInstrumentationLabeled
appInstrumented := (item.AppInstrumentationLabeled != nil && *item.AppInstrumentationLabeled)
appInstrumentationInherited := item.AppInstrumentationLabeled == nil
// check if the entire namespace is instrumented
// as it affects the applications in the namespace
// which use this label to determine if they should be instrumented
nsInstrumentationLabeled := nsInstrumentedMap[item.nameSpace]
item.nsItem.NsInstrumentationLabeled = nsInstrumentationLabeled
appInstrumented := (item.nsItem.AppInstrumentationLabeled != nil && *item.nsItem.AppInstrumentationLabeled)
appInstrumentationInherited := item.nsItem.AppInstrumentationLabeled == nil
nsInstrumented := (nsInstrumentationLabeled != nil && *nsInstrumentationLabeled)
item.InstrumentationEffective = appInstrumented || (appInstrumentationInherited && nsInstrumented)
item.nsItem.InstrumentationEffective = appInstrumented || (appInstrumentationInherited && nsInstrumented)
}

c.JSON(http.StatusOK, GetApplicationsInNamespaceResponse{
Applications: items,
})
return items, nil
}

func getDeployments(namespace string, ctx context.Context) ([]GetApplicationItem, error) {
Expand All @@ -102,17 +133,15 @@ func getDeployments(namespace string, ctx context.Context) ([]GetApplicationItem

response := make([]GetApplicationItem, len(deps.Items))
for i, dep := range deps.Items {
instrumentationLabel, found := dep.Labels[consts.OdigosInstrumentationLabel]
var appInstrumentationLabeled *bool
if found {
instrumentationLabel := instrumentationLabel == consts.InstrumentationEnabled
appInstrumentationLabeled = &instrumentationLabel
}
appInstrumentationLabeled := isObjectLabeledForInstrumentation(dep.ObjectMeta)
response[i] = GetApplicationItem{
Name: dep.Name,
Kind: WorkloadKindDeployment,
Instances: int(dep.Status.AvailableReplicas),
AppInstrumentationLabeled: appInstrumentationLabeled,
nameSpace: dep.Namespace,
nsItem: GetApplicationItemInNamespace {
Name: dep.Name,
Kind: WorkloadKindDeployment,
Instances: int(dep.Status.AvailableReplicas),
AppInstrumentationLabeled: appInstrumentationLabeled,
},
}
}

Expand All @@ -128,9 +157,12 @@ func getStatefulSets(namespace string, ctx context.Context) ([]GetApplicationIte
response := make([]GetApplicationItem, len(ss.Items))
for i, s := range ss.Items {
response[i] = GetApplicationItem{
Name: s.Name,
Kind: WorkloadKindStatefulSet,
Instances: int(s.Status.ReadyReplicas),
nameSpace: s.Namespace,
nsItem: GetApplicationItemInNamespace {
Name: s.Name,
Kind: WorkloadKindStatefulSet,
Instances: int(s.Status.ReadyReplicas),
},
}
}

Expand All @@ -146,9 +178,12 @@ func getDaemonSets(namespace string, ctx context.Context) ([]GetApplicationItem,
response := make([]GetApplicationItem, len(dss.Items))
for i, ds := range dss.Items {
response[i] = GetApplicationItem{
Name: ds.Name,
Kind: WorkloadKindDaemonSet,
Instances: int(ds.Status.NumberReady),
nameSpace: ds.Namespace,
nsItem: GetApplicationItemInNamespace {
Name: ds.Name,
Kind: WorkloadKindDaemonSet,
Instances: int(ds.Status.NumberReady),
},
}
}

Expand Down
79 changes: 60 additions & 19 deletions frontend/endpoints/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (

"golang.org/x/sync/errgroup"

"github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/common/consts"
"github.com/odigos-io/odigos/common/utils"

"github.com/odigos-io/odigos/frontend/kube"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

Expand All @@ -28,31 +30,32 @@ type GetNamespaceItem struct {
}

func GetNamespaces(c *gin.Context, odigosns string) {
ctx := c.Request.Context()
var (
relevantNameSpaces []v1.Namespace
appsPerNamespace map[string]int
)

odigosConfig, err := kube.DefaultClient.OdigosClient.OdigosConfigurations(odigosns).Get(c.Request.Context(), "odigos-config", metav1.GetOptions{})
if err != nil {
returnError(c, err)
return
}

list, err := kube.DefaultClient.CoreV1().Namespaces().List(c.Request.Context(), metav1.ListOptions{})
if err != nil {
returnError(c, err)
return
}

appsPerNamespace, err := CountAppsPerNamespace(c)
if err != nil {
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
var err error
relevantNameSpaces, err = getRelevantNameSpaces(ctx, odigosns)
return err
})

g.Go(func() error {
var err error
appsPerNamespace, err = CountAppsPerNamespace(ctx)
return err
})

if err := g.Wait(); err != nil {
returnError(c, err)
return
}

var response GetNamespacesResponse
for _, namespace := range list.Items {
if utils.IsNamespaceIgnored(namespace.Name, odigosConfig.Spec.IgnoredNamespaces) {
continue
}

for _, namespace := range relevantNameSpaces {
// check if entire namespace is instrumented
selected := namespace.Labels[consts.OdigosInstrumentationLabel] == consts.InstrumentationEnabled

Expand All @@ -66,6 +69,43 @@ func GetNamespaces(c *gin.Context, odigosns string) {
c.JSON(http.StatusOK, response)
}

// getRelevantNameSpaces returns a list of namespaces that are relevant for instrumentation.
// Taking into account the ignored namespaces from the OdigosConfiguration.
func getRelevantNameSpaces(ctx context.Context, odigosns string) ([]v1.Namespace, error) {
var (
odigosConfig *v1alpha1.OdigosConfiguration
list *v1.NamespaceList
)

g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
var err error
odigosConfig, err = kube.DefaultClient.OdigosClient.OdigosConfigurations(odigosns).Get(ctx, consts.DefaultOdigosConfigurationName, metav1.GetOptions{})
return err
})

g.Go(func() error {
var err error
list, err = kube.DefaultClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
return err
})

if err := g.Wait(); err != nil {
return []v1.Namespace{}, err
}

result := []v1.Namespace{}
for _, namespace := range list.Items {
if utils.IsNamespaceIgnored(namespace.Name, odigosConfig.Spec.IgnoredNamespaces) {
continue
}

result = append(result, namespace)
}

return result, nil
}

type PersistNamespaceItem struct {
Name string `json:"name"`
SelectedAll bool `json:"selected_all"`
Expand Down Expand Up @@ -116,6 +156,7 @@ func getJsonMergePatchForInstrumentationLabel(enabled *bool) []byte {
jsonMergePatchContent := fmt.Sprintf(`{"metadata":{"labels":{"%s":%s}}}`, consts.OdigosInstrumentationLabel, labelJsonMergePatchValue)
return []byte(jsonMergePatchContent)
}

func syncWorkloadsInNamespace(ctx context.Context, nsName string, workloads []PersistNamespaceObject) error {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(kube.K8sClientDefaultBurst)
Expand Down
Loading
Loading