mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 10:19:54 +00:00
feat: Install goldpinger daemonset if one does not exist when running goldpinger collector (#1619)
* feat: Install goldpinger if one does not exist when running goldpinger collector - Deploy golpinger daemonset if one is not detected in the cluster - Clean up all deployed resources - Add delay to allow users to wait for goldpinger to perform checks Signed-off-by: Evans Mungai <evans@replicated.com> * Add missing test data file Signed-off-by: Evans Mungai <evans@replicated.com> * Better naming of create resource functions Signed-off-by: Evans Mungai <evans@replicated.com> --------- Signed-off-by: Evans Mungai <evans@replicated.com>
This commit is contained in:
@@ -348,6 +348,8 @@ spec:
|
||||
type: object
|
||||
goldpinger:
|
||||
properties:
|
||||
collectDelay:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -374,6 +376,8 @@ spec:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
helm:
|
||||
properties:
|
||||
|
||||
@@ -2077,6 +2077,8 @@ spec:
|
||||
type: object
|
||||
goldpinger:
|
||||
properties:
|
||||
collectDelay:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -2103,6 +2105,8 @@ spec:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
helm:
|
||||
properties:
|
||||
|
||||
@@ -2108,6 +2108,8 @@ spec:
|
||||
type: object
|
||||
goldpinger:
|
||||
properties:
|
||||
collectDelay:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -2134,6 +2136,8 @@ spec:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountName:
|
||||
type: string
|
||||
type: object
|
||||
helm:
|
||||
properties:
|
||||
|
||||
@@ -96,6 +96,18 @@ func TestAnalyzeGoldpinger_podPingsAnalysis(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no ping errors for one pod",
|
||||
cao: caoFixture(t, "goldpinger/checkall-one-pod.json"),
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pings to \"gp-goldpinger-xn9rg\" pod succeeded",
|
||||
Message: "Pings to \"gp-goldpinger-xn9rg\" pod from all other pods in the cluster succeeded",
|
||||
IconKey: "kubernetes",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -277,9 +277,11 @@ type Helm struct {
|
||||
}
|
||||
|
||||
type Goldpinger struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
PodLaunchOptions *PodLaunchOptions `json:"podLaunchOptions,omitempty" yaml:"podLaunchOptions,omitempty"`
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
ServiceAccountName string `json:"serviceAccountName,omitempty" yaml:"serviceAccountName,omitempty"`
|
||||
CollectDelay string `json:"collectDelay,omitempty" yaml:"collectDelay,omitempty"`
|
||||
PodLaunchOptions *PodLaunchOptions `json:"podLaunchOptions,omitempty" yaml:"podLaunchOptions,omitempty"`
|
||||
}
|
||||
|
||||
type PodLaunchOptions struct {
|
||||
|
||||
@@ -14,11 +14,16 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// Collect goldpinger results from goldpinger service running in a cluster
|
||||
@@ -47,9 +52,26 @@ func (c *CollectGoldpinger) Collect(progressChan chan<- interface{}) (CollectorR
|
||||
var results []byte
|
||||
var err error
|
||||
|
||||
// Check if we have goldpinger running in the cluster, if not, lets deploy it and collect results
|
||||
namespace := "default"
|
||||
if c.Collector.Namespace != "" {
|
||||
namespace = c.Collector.Namespace
|
||||
}
|
||||
|
||||
url, resources, err := c.DiscoverOrCreateGoldpinger(namespace)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to ensure goldpinger is running: %v", err)
|
||||
return nil, errors.Wrap(err, "failed to ensure goldpinger is running")
|
||||
}
|
||||
defer func() {
|
||||
if err := c.cleanupResources(resources); err != nil {
|
||||
klog.Errorf("Failed to cleanup resources: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if util.IsInCluster() {
|
||||
klog.V(2).Infof("Collector running in cluster, querying goldpinger endpoint straight away")
|
||||
results, err = c.fetchCheckAllOutput()
|
||||
results, err = c.fetchCheckAllOutput(url)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Failed to query goldpinger endpoint in cluster: %v", err)
|
||||
klog.V(2).Infof(errMsg)
|
||||
@@ -58,7 +80,7 @@ func (c *CollectGoldpinger) Collect(progressChan chan<- interface{}) (CollectorR
|
||||
}
|
||||
} else {
|
||||
klog.V(2).Infof("Launch pod to query goldpinger endpoint then collect results from pod logs")
|
||||
results, err = c.runPodAndCollectGPResults(progressChan)
|
||||
results, err = c.runPodAndCollectGPResults(url, progressChan)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Failed to run pod to collect goldpinger results: %v", err)
|
||||
klog.V(2).Infof(errMsg)
|
||||
@@ -71,12 +93,366 @@ func (c *CollectGoldpinger) Collect(progressChan chan<- interface{}) (CollectorR
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) fetchCheckAllOutput() ([]byte, error) {
|
||||
// cleanupResources collects all created resources for later deletion
|
||||
// If creation of any resource fails, the already created resources
|
||||
// will be deleted
|
||||
type createdResources struct {
|
||||
Role *rbacv1.Role
|
||||
RoleBinding *rbacv1.RoleBinding
|
||||
DaemonSet *appsv1.DaemonSet
|
||||
ServiceAccnt *corev1.ServiceAccount
|
||||
Service *corev1.Service
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) cleanupResources(resources createdResources) error {
|
||||
var errs []error
|
||||
if resources.Service != nil {
|
||||
if err := c.Client.CoreV1().Services(resources.Service.Namespace).Delete(c.Context, resources.Service.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to delete Service %s", resources.Service.Name))
|
||||
}
|
||||
klog.V(2).Infof("%s Service deleted", resources.Service.Name)
|
||||
}
|
||||
|
||||
if resources.DaemonSet != nil {
|
||||
if err := c.Client.AppsV1().DaemonSets(resources.DaemonSet.Namespace).Delete(c.Context, resources.DaemonSet.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to delete DaemonSet %s", resources.DaemonSet.Name))
|
||||
}
|
||||
klog.V(2).Infof("%s DaemonSet deleted", resources.DaemonSet.Name)
|
||||
}
|
||||
|
||||
if resources.ServiceAccnt != nil {
|
||||
if err := c.Client.CoreV1().ServiceAccounts(resources.ServiceAccnt.Namespace).Delete(c.Context, resources.ServiceAccnt.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to delete ServiceAccount %s", resources.ServiceAccnt.Name))
|
||||
}
|
||||
klog.V(2).Infof("%s ServiceAccount deleted", resources.ServiceAccnt.Name)
|
||||
}
|
||||
|
||||
if resources.RoleBinding != nil {
|
||||
if err := c.Client.RbacV1().RoleBindings(resources.RoleBinding.Namespace).Delete(c.Context, resources.RoleBinding.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to delete RoleBinding %s", resources.RoleBinding.Name))
|
||||
}
|
||||
klog.V(2).Infof("%s RoleBinding deleted", resources.RoleBinding.Name)
|
||||
}
|
||||
|
||||
if resources.Role != nil {
|
||||
if err := c.Client.RbacV1().Roles(resources.Role.Namespace).Delete(c.Context, resources.Role.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to delete Role %s", resources.Role.Name))
|
||||
}
|
||||
klog.V(2).Infof("%s Role deleted", resources.Role.Name)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Errorf("failed to cleanup resources: %v", errs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUrlFromService(svc *corev1.Service) string {
|
||||
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%d/check_all", svc.Name, svc.Namespace, svc.Spec.Ports[0].Port)
|
||||
}
|
||||
|
||||
func parseCollectDelay(delay, defaultDelay string) (time.Duration, error) {
|
||||
if delay == "" {
|
||||
delay = defaultDelay
|
||||
}
|
||||
return time.ParseDuration(delay)
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) DiscoverOrCreateGoldpinger(ns string) (string, createdResources, error) {
|
||||
// Check if goldpinger is running in the cluster by searching for goldpinger's service
|
||||
ret := createdResources{}
|
||||
gpSvc, err := c.getGoldpingerService(ns)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to get goldpinger service")
|
||||
}
|
||||
|
||||
if gpSvc != nil {
|
||||
klog.V(2).Infof("Goldpinger service already exists")
|
||||
// By default, no delay needed if goldpinger service already exists
|
||||
delay, err := parseCollectDelay(c.Collector.CollectDelay, "0s")
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to parse duration")
|
||||
}
|
||||
time.Sleep(delay)
|
||||
return getUrlFromService(gpSvc), ret, nil
|
||||
}
|
||||
|
||||
// If we deploy GP, we need to wait for it to ping pods
|
||||
// Defaults to REFRESH_INTERVAL + CHECK_ALL_TIMEOUT
|
||||
delay, err := parseCollectDelay(c.Collector.CollectDelay, "6s")
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to parse duration")
|
||||
}
|
||||
|
||||
serviceAccountName := c.Collector.ServiceAccountName
|
||||
if serviceAccountName == "" {
|
||||
serviceAccountName = "ts-goldpinger-serviceaccount"
|
||||
|
||||
svcAcc, err := c.createGoldpingerServiceAccount(ns, serviceAccountName)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to create goldpinger service account")
|
||||
}
|
||||
ret.ServiceAccnt = svcAcc
|
||||
klog.V(2).Infof("%s ServiceAccount created", svcAcc.Name)
|
||||
|
||||
r, err := c.createGoldpingerRole(ns)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to create goldpinger role")
|
||||
}
|
||||
ret.Role = r
|
||||
klog.V(2).Infof("%s Role created", r.Name)
|
||||
|
||||
rb, err := c.createGoldpingerRoleBinding(ns)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to create goldpinger role binding")
|
||||
}
|
||||
ret.RoleBinding = rb
|
||||
klog.V(2).Infof("%s RoleBinding created", rb.Name)
|
||||
} else {
|
||||
if err := checkForExistingServiceAccount(c.Context, c.Client, ns, serviceAccountName); err != nil {
|
||||
return "", ret, err
|
||||
}
|
||||
}
|
||||
|
||||
ds, err := c.createGoldpingerDaemonSet(ns, serviceAccountName)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to create goldpinger daemonset")
|
||||
}
|
||||
ret.DaemonSet = ds
|
||||
klog.V(2).Infof("%s DaemonSet created", ds.Name)
|
||||
|
||||
// block till DaemonSet has right number of scheduled Pods
|
||||
timeoutCtx, cancel := context.WithTimeout(c.Context, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
err = waitForDaemonSetPods(timeoutCtx, c.Client, ds)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrapf(err, "failed to wait for %s DaemonSet pods", ds.Name)
|
||||
}
|
||||
klog.V(2).Infof("DaemonSet %s has desired number of pods", ds.Name)
|
||||
|
||||
time.Sleep(delay)
|
||||
|
||||
svc, err := c.createGoldpingerService(ns)
|
||||
if err != nil {
|
||||
return "", ret, errors.Wrap(err, "failed to create goldpinger service")
|
||||
}
|
||||
klog.V(2).Infof("%s Service created", svc.Name)
|
||||
ret.Service = svc
|
||||
|
||||
return getUrlFromService(svc), ret, nil
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) createGoldpingerServiceAccount(ns, name string) (*corev1.ServiceAccount, error) {
|
||||
svcAcc := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
|
||||
return c.Client.CoreV1().ServiceAccounts(ns).Create(c.Context, svcAcc, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) createGoldpingerRole(ns string) (*rbacv1.Role, error) {
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ts-goldpinger-role",
|
||||
Namespace: ns,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods"},
|
||||
Verbs: []string{"get", "list"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return c.Client.RbacV1().Roles(ns).Create(c.Context, role, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) createGoldpingerRoleBinding(ns string) (*rbacv1.RoleBinding, error) {
|
||||
roleBinding := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ts-goldpinger-rolebinding",
|
||||
Namespace: ns,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "ts-goldpinger-serviceaccount",
|
||||
Namespace: ns,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
Kind: "Role",
|
||||
Name: "ts-goldpinger-role",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
},
|
||||
}
|
||||
|
||||
return c.Client.RbacV1().RoleBindings(ns).Create(c.Context, roleBinding, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) createGoldpingerDaemonSet(ns, svcAccName string) (*appsv1.DaemonSet, error) {
|
||||
ds := &appsv1.DaemonSet{}
|
||||
|
||||
ds.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: "ts-goldpinger",
|
||||
Namespace: ns,
|
||||
Labels: gpNameLabels(),
|
||||
}
|
||||
|
||||
ds.Spec = appsv1.DaemonSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: gpNameLabels(),
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: gpNameLabels(),
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
PriorityClassName: "system-node-critical",
|
||||
ServiceAccountName: svcAccName,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "goldpinger-daemon",
|
||||
Image: "bloomberg/goldpinger:3.10.1",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "HOSTNAME",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
FieldPath: "spec.nodeName",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "REFRESH_INTERVAL",
|
||||
Value: "3", // Refresh interval in seconds. Its not a duration, its a number
|
||||
},
|
||||
{
|
||||
Name: "CHECK_ALL_TIMEOUT",
|
||||
Value: "3s",
|
||||
},
|
||||
{
|
||||
Name: "HOST",
|
||||
Value: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
Name: "PORT",
|
||||
Value: "8080",
|
||||
},
|
||||
{
|
||||
Name: "LABEL_SELECTOR",
|
||||
Value: gpNameLabelSelector(),
|
||||
},
|
||||
},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
ReadOnlyRootFilesystem: ptr.To(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
ContainerPort: 8080,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/",
|
||||
Port: intstr.FromString("http"),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/",
|
||||
Port: intstr.FromString("http"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceMemory: resource.MustParse("64Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
FSGroup: ptr.To(int64(2000)),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
RunAsUser: ptr.To(int64(1000)),
|
||||
SeccompProfile: &corev1.SeccompProfile{
|
||||
Type: "RuntimeDefault",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return c.Client.AppsV1().DaemonSets(ns).Create(c.Context, ds, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) createGoldpingerService(ns string) (*corev1.Service, error) {
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ts-goldpinger",
|
||||
Namespace: ns,
|
||||
Labels: gpNameLabels(),
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Port: 80,
|
||||
TargetPort: intstr.FromInt(8080),
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
Name: "http",
|
||||
},
|
||||
},
|
||||
Selector: gpNameLabels(),
|
||||
},
|
||||
}
|
||||
|
||||
return c.Client.CoreV1().Services(ns).Create(c.Context, svc, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) getGoldpingerService(ns string) (*corev1.Service, error) {
|
||||
svcs, err := c.Client.CoreV1().Services(ns).List(c.Context, metav1.ListOptions{
|
||||
LabelSelector: gpNameLabelSelector(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to list goldpinger services")
|
||||
}
|
||||
|
||||
if len(svcs.Items) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &svcs.Items[0], nil
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) fetchCheckAllOutput(url string) ([]byte, error) {
|
||||
client := &http.Client{
|
||||
Timeout: time.Minute, // Long enough timeout
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(c.Context, "GET", c.endpoint(), nil)
|
||||
req, err := http.NewRequestWithContext(c.Context, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,9 +475,7 @@ func (c *CollectGoldpinger) fetchCheckAllOutput() ([]byte, error) {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) runPodAndCollectGPResults(progressChan chan<- interface{}) ([]byte, error) {
|
||||
rest.InClusterConfig()
|
||||
|
||||
func (c *CollectGoldpinger) runPodAndCollectGPResults(url string, progressChan chan<- interface{}) ([]byte, error) {
|
||||
namespace := "default"
|
||||
serviceAccountName := ""
|
||||
image := constants.GP_DEFAULT_IMAGE
|
||||
@@ -144,7 +518,7 @@ func (c *CollectGoldpinger) runPodAndCollectGPResults(progressChan chan<- interf
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
Name: collectorContainerName,
|
||||
Command: []string{"wget"},
|
||||
Args: []string{"-q", "-O-", c.endpoint()},
|
||||
Args: []string{"-q", "-O-", url},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("50m"),
|
||||
@@ -162,7 +536,7 @@ func (c *CollectGoldpinger) runPodAndCollectGPResults(progressChan chan<- interf
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
// Pass an empty bundle path since we don't need to save the results
|
||||
runPodCollector := &CollectRunPod{runPodSpec, "", c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
runPodCollector := &CollectRunPod{runPodSpec, "", c.Collector.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
|
||||
output, err := runPodCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
@@ -204,11 +578,12 @@ func (c *CollectGoldpinger) runPodAndCollectGPResults(progressChan chan<- interf
|
||||
return podLogs, nil
|
||||
}
|
||||
|
||||
func (c *CollectGoldpinger) endpoint() string {
|
||||
namespace := c.Collector.Namespace
|
||||
if namespace == "" {
|
||||
namespace = constants.GP_DEFAULT_NAMESPACE
|
||||
func gpNameLabels() map[string]string {
|
||||
return map[string]string{
|
||||
"app.kubernetes.io/name": "goldpinger",
|
||||
}
|
||||
|
||||
return fmt.Sprintf("http://goldpinger.%s.svc.cluster.local:80/check_all", namespace)
|
||||
}
|
||||
|
||||
func gpNameLabelSelector() string {
|
||||
return "app.kubernetes.io/name=goldpinger"
|
||||
}
|
||||
|
||||
@@ -475,6 +475,9 @@
|
||||
"goldpinger": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"collectDelay": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -514,6 +517,9 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceAccountName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3139,6 +3139,9 @@
|
||||
"goldpinger": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"collectDelay": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3178,6 +3181,9 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceAccountName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3185,6 +3185,9 @@
|
||||
"goldpinger": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"collectDelay": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectorName": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3224,6 +3227,9 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serviceAccountName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,19 +10,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/e2e-framework/klient/wait"
|
||||
"sigs.k8s.io/e2e-framework/klient/wait/conditions"
|
||||
"sigs.k8s.io/e2e-framework/pkg/envconf"
|
||||
"sigs.k8s.io/e2e-framework/pkg/features"
|
||||
"sigs.k8s.io/e2e-framework/third_party/helm"
|
||||
)
|
||||
|
||||
var specTemplate = `
|
||||
@@ -38,43 +31,13 @@ spec:
|
||||
exclude: true
|
||||
- goldpinger:
|
||||
namespace: $NAMESPACE
|
||||
collectDelay: 20s
|
||||
analyzers:
|
||||
- goldpinger: {}
|
||||
`
|
||||
|
||||
func Test_GoldpingerCollector(t *testing.T) {
|
||||
releaseName := "goldpinger"
|
||||
|
||||
feature := features.New("Goldpinger collector and analyser").
|
||||
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
cluster := getClusterFromContext(t, ctx, ClusterName)
|
||||
|
||||
manager := helm.New(cluster.GetKubeconfig())
|
||||
err := manager.RunInstall(
|
||||
helm.WithName(releaseName),
|
||||
helm.WithNamespace(c.Namespace()),
|
||||
helm.WithChart(testutils.TestFixtureFilePath(t, "charts/goldpinger-6.0.1.tgz")),
|
||||
helm.WithWait(),
|
||||
helm.WithTimeout("2m"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := c.NewClient()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Lets wait for the goldpinger pods to be running
|
||||
ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: "goldpinger", Namespace: c.Namespace()}}
|
||||
err = wait.For(
|
||||
conditions.New(client.Resources()).DaemonSetReady(ds),
|
||||
wait.WithTimeout(time.Second*30),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// HACK: wait for goldpinger to do its thing
|
||||
time.Sleep(time.Second * 30)
|
||||
|
||||
return ctx
|
||||
}).
|
||||
Assess("collect and analyse goldpinger pings", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
var out bytes.Buffer
|
||||
var stdErr bytes.Buffer
|
||||
@@ -107,8 +70,8 @@ func Test_GoldpingerCollector(t *testing.T) {
|
||||
|
||||
// Check that we analysed collected goldpinger results.
|
||||
// We should expect a single analysis result for goldpinger.
|
||||
assert.Equal(t, 1, len(analysisResults))
|
||||
assert.True(t, strings.HasPrefix(analysisResults[0].Name, "pings.to.goldpinger."))
|
||||
require.Equal(t, 1, len(analysisResults))
|
||||
assert.True(t, strings.HasPrefix(analysisResults[0].Name, "pings.to.ts.goldpinger."))
|
||||
if t.Failed() {
|
||||
t.Logf("Analysis results: %s\n", analysisJSON)
|
||||
t.Logf("Stdout: %s\n", out.String())
|
||||
@@ -117,14 +80,6 @@ func Test_GoldpingerCollector(t *testing.T) {
|
||||
}
|
||||
|
||||
return ctx
|
||||
}).
|
||||
Teardown(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
|
||||
cluster := getClusterFromContext(t, ctx, ClusterName)
|
||||
manager := helm.New(cluster.GetKubeconfig())
|
||||
err := manager.RunUninstall(helm.WithName(releaseName), helm.WithNamespace(c.Namespace()))
|
||||
require.NoError(t, err)
|
||||
return ctx
|
||||
}).
|
||||
Feature()
|
||||
}).Feature()
|
||||
testenv.Test(t, feature)
|
||||
}
|
||||
|
||||
BIN
testdata/charts/goldpinger-6.0.1.tgz
vendored
BIN
testdata/charts/goldpinger-6.0.1.tgz
vendored
Binary file not shown.
30
testdata/goldpinger/checkall-one-pod.json
vendored
Normal file
30
testdata/goldpinger/checkall-one-pod.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostIP": "172.18.0.3",
|
||||
"podIP": "10.42.0.17",
|
||||
"podName": "gp-goldpinger-xn9rg"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"gp-goldpinger-xn9rg": {
|
||||
"HostIP": "172.18.0.3",
|
||||
"OK": true,
|
||||
"PodIP": "10.42.0.17",
|
||||
"response": {
|
||||
"podResults": {
|
||||
"gp-goldpinger-xn9rg": {
|
||||
"HostIP": "172.18.0.3",
|
||||
"OK": true,
|
||||
"PingTime": "2024-09-24T07:10:10.431Z",
|
||||
"PodIP": "10.42.0.17",
|
||||
"response": {
|
||||
"boot_time": "2024-09-24T07:09:40.411Z"
|
||||
},
|
||||
"status-code": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user