Skip to content

Commit db5f690

Browse files
committed
This is a combination of 2 commits.
This is the 1st commit message: wip This is the commit message openshift#2:
1 parent 7de770d commit db5f690

11 files changed

+624
-2
lines changed

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ GO_PKG := github.com/operator-framework
2020
REGISTRY_PKG := $(GO_PKG)/operator-registry
2121
OLM_PKG := $(GO_PKG)/operator-lifecycle-manager
2222
API_PKG := $(GO_PKG)/api
23+
ROOT_PKG := github.com/openshift/operator-framework-olm
2324

25+
COLLECT_PROFILES_CMD := $(addprefix bin/, collect-profiles)
2426
OPM := $(addprefix bin/, opm)
2527
OLM_CMDS := $(shell go list -mod=vendor $(OLM_PKG)/cmd/...)
2628
REGISTRY_CMDS := $(addprefix bin/, $(shell ls staging/operator-registry/cmd | grep -v opm))
27-
29+
OPENSHIFT_CMDS := $(shell go list -mod=vendor github.com/openshift/operator-framework-olm/cmd/...)
2830
# Phony prerequisite for targets that rely on the go build cache to determine staleness.
2931
.PHONY: FORCE
3032
FORCE:
@@ -53,7 +55,7 @@ build/registry:
5355
$(MAKE) $(REGISTRY_CMDS) $(OPM)
5456

5557
build/olm:
56-
$(MAKE) $(OLM_CMDS)
58+
$(MAKE) $(OPENSHIFT_CMDS) $(OLM_CMDS)
5759

5860
$(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)'"
5961
$(OPM):
@@ -67,6 +69,9 @@ $(OLM_CMDS): version_flags=-ldflags "-X $(OLM_PKG)/pkg/version.GitCommit=$(GIT_C
6769
$(OLM_CMDS):
6870
go build $(version_flags) $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o bin/$(shell basename $@) $@
6971

72+
$(OPENSHIFT_CMDS): FORCE
73+
go build $(GO_BUILD_OPTS) $(GO_BUILD_TAGS) -o $(COLLECT_PROFILES_CMD) $(ROOT_PKG)/cmd/collect-profiles
74+
7075
.PHONY: cross
7176
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)'"
7277
cross:

cmd/collect-profiles/main.go

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"crypto/tls"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
14+
"github.com/spf13/cobra"
15+
corev1 "k8s.io/api/core/v1"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/klog/v2"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
20+
"github.com/openshift/operator-framework-olm/pkg/profiling/config"
21+
"github.com/openshift/operator-framework-olm/pkg/version"
22+
)
23+
24+
const (
25+
profileConfigMapLabelKey = "olm.openshift.io/pprof"
26+
)
27+
28+
var (
29+
rootCmd = newCmd()
30+
31+
// Used for flags
32+
namespace string
33+
configPath string
34+
clientCAPath string
35+
)
36+
37+
func init() {
38+
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The Kubernetes namespace where the generated configMaps should exist. Defaults to \"default\".")
39+
rootCmd.MarkFlagRequired("namespace")
40+
rootCmd.PersistentFlags().StringVarP(&configPath, "config-path", "c", "/etc/config/config.yaml", "The path to the collect-profiles configuration file.")
41+
rootCmd.MarkFlagRequired("config-file")
42+
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.")
43+
}
44+
45+
func Execute() {
46+
if err := rootCmd.Execute(); err != nil {
47+
klog.Fatal(err)
48+
os.Exit(1)
49+
}
50+
}
51+
52+
func getTruePointer() *bool {
53+
trueBool := true
54+
return &trueBool
55+
}
56+
57+
func newCmd() *cobra.Command {
58+
var cfg config.Configuration
59+
return &cobra.Command{
60+
Use: "collect-profiles endpoint:argument",
61+
Short: "Retrieve the pprof data from an endpoint and stores it in a configMap",
62+
Long: `The collect-profiles command makes https requests against pprof endpoints
63+
provided as arguments and stores that information in immutable configMaps.`,
64+
Version: version.String(),
65+
PersistentPreRunE: func(*cobra.Command, []string) error {
66+
return cfg.Load()
67+
},
68+
RunE: func(cmd *cobra.Command, args []string) error {
69+
if len(args) < 1 {
70+
return fmt.Errorf("must specify endpoint")
71+
}
72+
73+
jobConfig, err := config.GetConfig(configPath)
74+
if err != nil {
75+
klog.Infof("error retrieving job config")
76+
return err
77+
}
78+
79+
// Exit if job is disabled
80+
if jobConfig.Disabled {
81+
klog.Infof("CronJob disabled, exiting")
82+
return nil
83+
}
84+
85+
// Validate input
86+
validatedArguments := make([]*argument, len(args))
87+
for i, arg := range args {
88+
a, err := newArgument(arg)
89+
if err != nil {
90+
return err
91+
}
92+
validatedArguments[i] = a
93+
}
94+
95+
// Get existing configmaps
96+
existingConfigMaps := &corev1.ConfigMapList{}
97+
if err := cfg.Client.List(cmd.Context(), existingConfigMaps, client.InNamespace(namespace), client.HasLabels{profileConfigMapLabelKey}); err != nil {
98+
return err
99+
}
100+
101+
newestConfigMaps, expiredConfigMaps := separateConfigMapsIntoNewestAndExpired(existingConfigMaps.Items)
102+
103+
// Attempt to delete all but the newest configMaps generated by this job
104+
errs := []error{}
105+
for _, cm := range expiredConfigMaps {
106+
if err := cfg.Client.Delete(cmd.Context(), &cm); err != nil {
107+
errs = append(errs, err) // log the delete error
108+
continue
109+
}
110+
klog.Infof("Successfully deleted configMap %s/%s", cm.GetNamespace(), cm.GetName())
111+
}
112+
113+
// If a delete call failed, abort to avoid creating new configMaps
114+
if len(errs) != 0 {
115+
return fmt.Errorf("error deleting expired pprof configMaps: %v", errs)
116+
}
117+
118+
httpClient, err := getHttpClient(clientCAPath)
119+
if err != nil {
120+
return err
121+
}
122+
123+
// Track successfully created configMaps by generateName for each endpoint being scrapped.
124+
createdCM := map[string]struct{}{}
125+
126+
for _, a := range validatedArguments {
127+
b, err := requestURLBody(httpClient, a.url)
128+
if err != nil {
129+
klog.Infof("error retrieving pprof profile: %v", err)
130+
continue
131+
}
132+
133+
cm := &corev1.ConfigMap{
134+
ObjectMeta: metav1.ObjectMeta{
135+
GenerateName: a.generateName,
136+
Namespace: namespace,
137+
Labels: map[string]string{
138+
profileConfigMapLabelKey: "",
139+
},
140+
},
141+
Immutable: getTruePointer(),
142+
BinaryData: map[string][]byte{
143+
"profile.pb.gz": b,
144+
},
145+
}
146+
147+
if err := cfg.Client.Create(cmd.Context(), cm); err != nil {
148+
klog.Errorf("error created configMap %s/%s: %v", cm.GetNamespace(), cm.GetName(), err)
149+
continue
150+
}
151+
152+
klog.Infof("Successfully created configMap %s/%s", cm.GetNamespace(), cm.GetName())
153+
createdCM[a.generateName] = struct{}{}
154+
}
155+
156+
// Delete the configMaps which are no longer the newest
157+
for _, cm := range newestConfigMaps {
158+
// Don't delete ConfigMaps that were not replaced
159+
// Also prevents deletes of configMaps with generateNames not included in command.
160+
if _, ok := createdCM[cm.GenerateName]; !ok {
161+
continue
162+
}
163+
if err := cfg.Client.Delete(cmd.Context(), &cm); err != nil {
164+
errs = append(errs, err)
165+
continue
166+
}
167+
klog.Infof("Successfully deleted configMap %s/%s", cm.GetNamespace(), cm.GetName())
168+
}
169+
170+
if len(errs) != 0 {
171+
return fmt.Errorf("error deleting existing pprof configMaps: %v", errs)
172+
}
173+
return nil
174+
},
175+
}
176+
}
177+
178+
func separateConfigMapsIntoNewestAndExpired(configMaps []corev1.ConfigMap) (newestCMs []corev1.ConfigMap, expiredCMs []corev1.ConfigMap) {
179+
// Group ConfigMaps by GenerateName
180+
newestConfigMaps := map[string]corev1.ConfigMap{}
181+
for _, cm := range configMaps {
182+
if _, ok := newestConfigMaps[cm.GenerateName]; !ok {
183+
newestConfigMaps[cm.GenerateName] = cm
184+
continue
185+
}
186+
if cm.CreationTimestamp.After(newestConfigMaps[cm.GenerateName].CreationTimestamp.Time) {
187+
newestConfigMaps[cm.GenerateName], cm = cm, newestConfigMaps[cm.GenerateName]
188+
}
189+
expiredCMs = append(expiredCMs, cm)
190+
}
191+
192+
for _, v := range newestConfigMaps {
193+
newestCMs = append(newestCMs, v)
194+
}
195+
196+
return newestCMs, expiredCMs
197+
}
198+
199+
type argument struct {
200+
generateName string
201+
url *url.URL
202+
}
203+
204+
func newArgument(s string) (*argument, error) {
205+
splitStrings := strings.SplitN(s, ":", 2)
206+
if len(splitStrings) != 2 {
207+
return nil, fmt.Errorf("Error")
208+
}
209+
210+
url, err := url.Parse(splitStrings[1])
211+
if err != nil {
212+
return nil, err
213+
}
214+
215+
if strings.ToLower(url.Scheme) != "https" {
216+
return nil, fmt.Errorf("URL Scheme must be HTTPS")
217+
}
218+
219+
arg := &argument{
220+
generateName: splitStrings[0],
221+
url: url,
222+
}
223+
224+
return arg, nil
225+
}
226+
227+
func getHttpClient(clientCAPath string) (*http.Client, error) {
228+
cert, err := tls.LoadX509KeyPair(filepath.Join(clientCAPath, corev1.TLSCertKey), filepath.Join(clientCAPath, corev1.TLSPrivateKeyKey))
229+
if err != nil {
230+
return nil, err
231+
}
232+
233+
return &http.Client{
234+
Transport: &http.Transport{
235+
TLSClientConfig: &tls.Config{
236+
InsecureSkipVerify: true,
237+
Certificates: []tls.Certificate{cert},
238+
},
239+
},
240+
}, nil
241+
}
242+
243+
func requestURLBody(httpClient *http.Client, u *url.URL) ([]byte, error) {
244+
response, err := httpClient.Do(&http.Request{
245+
Method: http.MethodGet,
246+
URL: u,
247+
})
248+
if err != nil {
249+
return nil, err
250+
}
251+
252+
if response.StatusCode != http.StatusOK {
253+
return nil, fmt.Errorf("%s responded with %d status code instead of %d", u, response.StatusCode, http.StatusOK)
254+
}
255+
256+
var b bytes.Buffer
257+
if _, err := io.Copy(&b, response.Body); err != nil {
258+
return nil, fmt.Errorf("error reading response body: %v", err)
259+
}
260+
261+
return b.Bytes(), nil
262+
}

0 commit comments

Comments
 (0)