Skip to content

Commit

Permalink
This is a combination of 2 commits.
Browse files Browse the repository at this point in the history
 This is the 1st commit message:

wip

 This is the commit message openshift#2:
  • Loading branch information
awgreene committed Jul 1, 2021
1 parent 7de770d commit b218a31
Show file tree
Hide file tree
Showing 11 changed files with 624 additions and 2 deletions.
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ GO_PKG := github.com/operator-framework
REGISTRY_PKG := $(GO_PKG)/operator-registry
OLM_PKG := $(GO_PKG)/operator-lifecycle-manager
API_PKG := $(GO_PKG)/api
ROOT_PKG := github.com/openshift/operator-framework-olm

COLLECT_PROFILES_CMD := $(addprefix bin/, collect-profiles)
OPM := $(addprefix bin/, opm)
OLM_CMDS := $(shell go list -mod=vendor $(OLM_PKG)/cmd/...)
REGISTRY_CMDS := $(addprefix bin/, $(shell ls staging/operator-registry/cmd | grep -v opm))

OPENSHIFT_CMDS := $(shell go list -mod=vendor github.com/openshift/operator-framework-olm/cmd/...)
# Phony prerequisite for targets that rely on the go build cache to determine staleness.
.PHONY: FORCE
FORCE:
Expand Down Expand Up @@ -53,7 +55,7 @@ build/registry:
$(MAKE) $(REGISTRY_CMDS) $(OPM)

build/olm:
$(MAKE) $(OLM_CMDS)
$(MAKE) $(OPENSHIFT_CMDS) $(OLM_CMDS)

$(OPM): version_flags=-ldflags "-X '$(REGISTRY_PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(REGISTRY_PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(REGISTRY_PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'"
$(OPM):
Expand All @@ -67,6 +69,9 @@ $(OLM_CMDS): version_flags=-ldflags "-X $(OLM_PKG)/pkg/version.GitCommit=$(GIT_C
$(OLM_CMDS):
go build $(version_flags) $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o bin/$(shell basename $@) $@

$(OPENSHIFT_CMDS): FORCE
go build $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o $(COLLECT_PROFILES_CMD) $(ROOT_PKG)/cmd/collect-profiles

.PHONY: cross
cross: version_flags=-ldflags "-X '$(REGISTRY_PKG)/cmd/opm/version.gitCommit=$(GIT_COMMIT)' -X '$(REGISTRY_PKG)/cmd/opm/version.opmVersion=$(OPM_VERSION)' -X '$(REGISTRY_PKG)/cmd/opm/version.buildDate=$(BUILD_DATE)'"
cross:
Expand Down
262 changes: 262 additions & 0 deletions cmd/collect-profiles/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package cmd

import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/openshift/operator-framework-olm/pkg/profiling/config"
"github.com/openshift/operator-framework-olm/pkg/version"
)

const (
profileConfigMapLabelKey = "olm.openshift.io/pprof"
)

var (
rootCmd = newCmd()

// Used for flags
namespace string
configPath string
clientCAPath string
)

func init() {
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The Kubernetes namespace where the generated configMaps should exist. Defaults to \"default\".")
rootCmd.MarkFlagRequired("namespace")
rootCmd.PersistentFlags().StringVarP(&configPath, "config-path", "c", "/etc/config/config.yaml", "The path to the collect-profiles configuration file.")
rootCmd.MarkFlagRequired("config-file")
rootCmd.PersistentFlags().StringVarP(&clientCAPath, "client-ca", "", "/etc/pki/tls/certs/", "The path to the tls cert used by the client making https requests against the pprof endpoints.")
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
klog.Fatal(err)
os.Exit(1)
}
}

func getTruePointer() *bool {
trueBool := true
return &trueBool
}

func newCmd() *cobra.Command {
var cfg config.Configuration
return &cobra.Command{
Use: "collect-profiles endpoint:argument",
Short: "Retrieve the pprof data from an endpoint and stores it in a configMap",
Long: `The collect-profiles command makes https requests against pprof endpoints
provided as arguments and stores that information in immutable configMaps.`,
Version: version.String(),
PersistentPreRunE: func(*cobra.Command, []string) error {
return cfg.Load()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify endpoint")
}

jobConfig, err := config.GetConfig(configPath)
if err != nil {
klog.Infof("error retrieving job config")
return err
}

// Exit if job is disabled
if jobConfig.Disabled {
klog.Infof("CronJob disabled, exiting")
return nil
}

// Validate input
validatedArguments := make([]*argument, len(args))
for i, arg := range args {
a, err := newArgument(arg)
if err != nil {
return err
}
validatedArguments[i] = a
}

// Get existing configmaps
existingConfigMaps := &corev1.ConfigMapList{}
if err := cfg.Client.List(cmd.Context(), existingConfigMaps, client.InNamespace(namespace), client.HasLabels{profileConfigMapLabelKey}); err != nil {
return err
}

newestConfigMaps, expiredConfigMaps := separateConfigMapsIntoNewestAndExpired(existingConfigMaps.Items)

// Attempt to delete all but the newest configMaps generated by this job
errs := []error{}
for _, cm := range expiredConfigMaps {
if err := cfg.Client.Delete(cmd.Context(), &cm); err != nil {
errs = append(errs, err) // log the delete error
continue
}
klog.Infof("Successfully deleted configMap %s/%s", cm.GetNamespace(), cm.GetName())
}

// If a delete call failed, abort to avoid creating new configMaps
if len(errs) != 0 {
return fmt.Errorf("error deleting expired pprof configMaps: %v", errs)
}

httpClient, err := getHttpClient(clientCAPath)
if err != nil {
return err
}

// Track successfully created configMaps by generateName for each endpoint being scrapped.
createdCM := map[string]struct{}{}

for _, a := range validatedArguments {
b, err := requestURLBody(httpClient, a.url)
if err != nil {
klog.Infof("error retrieving pprof profile: %v", err)
continue
}

cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
GenerateName: a.generateName,
Namespace: namespace,
Labels: map[string]string{
profileConfigMapLabelKey: "",
},
},
Immutable: getTruePointer(),
BinaryData: map[string][]byte{
"profile.pb.gz": b,
},
}

if err := cfg.Client.Create(cmd.Context(), cm); err != nil {
klog.Errorf("error created configMap %s/%s: %v", cm.GetNamespace(), cm.GetName(), err)
continue
}

klog.Infof("Successfully created configMap %s/%s", cm.GetNamespace(), cm.GetName())
createdCM[a.generateName] = struct{}{}
}

// Delete the configMaps which are no longer the newest
for _, cm := range newestConfigMaps {
// Don't delete ConfigMaps that were not replaced
// Also prevents deletes of configMaps with generateNames not included in command.
if _, ok := createdCM[cm.GenerateName]; !ok {
continue
}
if err := cfg.Client.Delete(cmd.Context(), &cm); err != nil {
errs = append(errs, err)
continue
}
klog.Infof("Successfully deleted configMap %s/%s", cm.GetNamespace(), cm.GetName())
}

if len(errs) != 0 {
return fmt.Errorf("error deleting existing pprof configMaps: %v", errs)
}
return nil
},
}
}

func separateConfigMapsIntoNewestAndExpired(configMaps []corev1.ConfigMap) (newestCMs []corev1.ConfigMap, expiredCMs []corev1.ConfigMap) {
// Group ConfigMaps by GenerateName
newestConfigMaps := map[string]corev1.ConfigMap{}
for _, cm := range configMaps {
if _, ok := newestConfigMaps[cm.GenerateName]; !ok {
newestConfigMaps[cm.GenerateName] = cm
continue
}
if cm.CreationTimestamp.After(newestConfigMaps[cm.GenerateName].CreationTimestamp.Time) {
newestConfigMaps[cm.GenerateName], cm = cm, newestConfigMaps[cm.GenerateName]
}
expiredCMs = append(expiredCMs, cm)
}

for _, v := range newestConfigMaps {
newestCMs = append(newestCMs, v)
}

return newestCMs, expiredCMs
}

type argument struct {
generateName string
url *url.URL
}

func newArgument(s string) (*argument, error) {
splitStrings := strings.SplitN(s, ":", 2)
if len(splitStrings) != 2 {
return nil, fmt.Errorf("Error")
}

url, err := url.Parse(splitStrings[1])
if err != nil {
return nil, err
}

if strings.ToLower(url.Scheme) != "https" {
return nil, fmt.Errorf("URL Scheme must be HTTPS")
}

arg := &argument{
generateName: splitStrings[0],
url: url,
}

return arg, nil
}

func getHttpClient(clientCAPath string) (*http.Client, error) {
cert, err := tls.LoadX509KeyPair(filepath.Join(clientCAPath, corev1.TLSCertKey), filepath.Join(clientCAPath, corev1.TLSPrivateKeyKey))
if err != nil {
return nil, err
}

return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
},
},
}, nil
}

func requestURLBody(httpClient *http.Client, u *url.URL) ([]byte, error) {
response, err := httpClient.Do(&http.Request{
Method: http.MethodGet,
URL: u,
})
if err != nil {
return nil, err
}

if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s responded with %d status code instead of %d", u, response.StatusCode, http.StatusOK)
}

var b bytes.Buffer
if _, err := io.Copy(&b, response.Body); err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}

return b.Bytes(), nil
}
Loading

0 comments on commit b218a31

Please sign in to comment.