Skip to content

Commit 4639ff6

Browse files
authored
[Feature] Change log level on the fly (#788)
1 parent 4615188 commit 4639ff6

File tree

15 files changed

+700
-26
lines changed

15 files changed

+700
-26
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ ifdef VERBOSE
123123
endif
124124

125125
EXCLUDE_DIRS := tests vendor .gobuild deps tools
126-
SOURCES_QUERY := find $(SRCDIR) -name '*.go' -type f -not -path '$(SRCDIR)/tests/*' -not -path '$(SRCDIR)/vendor/*' -not -path '$(SRCDIR)/.gobuild/*' -not -path '$(SRCDIR)/deps/*' -not -path '$(SRCDIR)/tools/*'
126+
SOURCES_QUERY := find ./ -type f -name '*.go' $(foreach EXCLUDE_DIR,$(EXCLUDE_DIRS), -not -path "./$(EXCLUDE_DIR)/*")
127127
SOURCES := $(shell $(SOURCES_QUERY))
128-
DASHBOARDSOURCES := $(shell find $(DASHBOARDDIR)/src -name '*.js' -not -path './test/*') $(DASHBOARDDIR)/package.json
128+
DASHBOARDSOURCES := $(shell find $(DASHBOARDDIR)/src -name '*.js') $(DASHBOARDDIR)/package.json
129129

130130
.DEFAULT_GOAL := all
131131
.PHONY: all

pkg/apis/deployment/v1/plan.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ const (
169169
// Runtime Updates
170170
// ActionTypeRuntimeContainerImageUpdate updates container image in runtime
171171
ActionTypeRuntimeContainerImageUpdate ActionType = "RuntimeContainerImageUpdate"
172+
// ActionTypeRuntimeContainerArgsLogLevelUpdate updates the container's executor arguments.
173+
ActionTypeRuntimeContainerArgsLogLevelUpdate ActionType = "RuntimeContainerArgsLogLevelUpdate"
172174
)
173175

174176
const (

pkg/apis/deployment/v2alpha1/plan.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ const (
171171
// Runtime Updates
172172
// ActionTypeRuntimeContainerImageUpdate updates container image in runtime
173173
ActionTypeRuntimeContainerImageUpdate ActionType = "RuntimeContainerImageUpdate"
174+
// ActionTypeRuntimeContainerArgsLogLevelUpdate updates the container's executor arguments.
175+
ActionTypeRuntimeContainerArgsLogLevelUpdate ActionType = "RuntimeContainerArgsLogLevelUpdate"
174176
)
175177

176178
const (

pkg/deployment/reconcile/action_helper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (a actionImpl) Timeout(deploymentSpec api.DeploymentSpec) time.Duration {
102102
return a.timeout(deploymentSpec)
103103
}
104104

105-
// Return the MemberID used / created in this action
105+
// MemberID returns the member ID used / created in the current action.
106106
func (a actionImpl) MemberID() string {
107107
return *a.memberIDRef
108108
}
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2021 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Tomasz Mielech
21+
//
22+
23+
package reconcile
24+
25+
import (
26+
"context"
27+
"fmt"
28+
"strings"
29+
30+
"github.com/pkg/errors"
31+
"github.com/rs/zerolog"
32+
core "k8s.io/api/core/v1"
33+
34+
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
35+
"github.com/arangodb/kube-arangodb/pkg/deployment/rotation"
36+
"github.com/arangodb/kube-arangodb/pkg/util/arangod"
37+
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
38+
)
39+
40+
func init() {
41+
registerAction(api.ActionTypeRuntimeContainerArgsLogLevelUpdate, runtimeContainerArgsUpdate)
42+
}
43+
44+
func runtimeContainerArgsUpdate(log zerolog.Logger, action api.Action, actionCtx ActionContext) Action {
45+
a := &actionRuntimeContainerArgsUpdate{}
46+
47+
a.actionImpl = newActionImplDefRef(log, action, actionCtx, defaultTimeout)
48+
49+
return a
50+
}
51+
52+
var _ ActionReloadCachedStatus = &actionRuntimeContainerArgsUpdate{}
53+
var _ ActionPost = &actionRuntimeContainerArgsUpdate{}
54+
55+
type actionRuntimeContainerArgsUpdate struct {
56+
// actionImpl implement timeout and member id functions
57+
actionImpl
58+
}
59+
60+
// Post updates arguments for the specific Arango member.
61+
func (a actionRuntimeContainerArgsUpdate) Post(ctx context.Context) error {
62+
63+
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
64+
if !ok {
65+
a.log.Info().Msg("member is gone already")
66+
return nil
67+
}
68+
69+
memberName := m.ArangoMemberName(a.actionCtx.GetName(), a.action.Group)
70+
member, ok := a.actionCtx.GetCachedStatus().ArangoMember(memberName)
71+
if !ok {
72+
return errors.Errorf("ArangoMember %s not found", memberName)
73+
}
74+
75+
containerName, ok := a.action.GetParam(rotation.ContainerName)
76+
if !ok {
77+
a.log.Warn().Msgf("Unable to find action's param %s", rotation.ContainerName)
78+
return nil
79+
}
80+
81+
log := a.log.With().Str("containerName", containerName).Logger()
82+
updateMemberStatusArgs := func(obj *api.ArangoMember, s *api.ArangoMemberStatus) bool {
83+
if obj.Spec.Template == nil || s.Template == nil ||
84+
obj.Spec.Template.PodSpec == nil || s.Template.PodSpec == nil {
85+
log.Info().Msgf("Nil Member definition")
86+
return false
87+
}
88+
89+
if len(obj.Spec.Template.PodSpec.Spec.Containers) != len(s.Template.PodSpec.Spec.Containers) {
90+
log.Info().Msgf("Invalid size of containers")
91+
return false
92+
}
93+
94+
for id := range obj.Spec.Template.PodSpec.Spec.Containers {
95+
if obj.Spec.Template.PodSpec.Spec.Containers[id].Name == containerName {
96+
if s.Template.PodSpec.Spec.Containers[id].Name != containerName {
97+
log.Info().Msgf("Invalid order of containers")
98+
return false
99+
}
100+
101+
s.Template.PodSpec.Spec.Containers[id].Command = obj.Spec.Template.PodSpec.Spec.Containers[id].Command
102+
log.Info().Msgf("Updating container args")
103+
return true
104+
}
105+
}
106+
107+
log.Info().Msgf("can not find the container")
108+
109+
return false
110+
}
111+
112+
err := a.actionCtx.WithArangoMemberStatusUpdate(ctx, member.GetNamespace(), member.GetName(), updateMemberStatusArgs)
113+
if err != nil {
114+
return errors.WithMessage(err, "Error while updating member status")
115+
}
116+
117+
return nil
118+
}
119+
120+
// ReloadCachedStatus reloads the inspector cache when the action is done.
121+
func (a actionRuntimeContainerArgsUpdate) ReloadCachedStatus() bool {
122+
return true
123+
}
124+
125+
// Start starts the action for changing conditions on the provided member.
126+
func (a actionRuntimeContainerArgsUpdate) Start(ctx context.Context) (bool, error) {
127+
128+
m, ok := a.actionCtx.GetMemberStatusByID(a.action.MemberID)
129+
if !ok {
130+
a.log.Info().Msg("member is gone already")
131+
return true, nil
132+
}
133+
134+
if !m.Phase.IsReady() {
135+
a.log.Info().Msg("Member is not ready, unable to run update operation")
136+
return true, nil
137+
}
138+
139+
containerName, ok := a.action.GetParam(rotation.ContainerName)
140+
if !ok {
141+
return true, nil
142+
}
143+
144+
memberName := m.ArangoMemberName(a.actionCtx.GetName(), a.action.Group)
145+
member, ok := a.actionCtx.GetCachedStatus().ArangoMember(memberName)
146+
if !ok {
147+
return false, errors.Errorf("ArangoMember %s not found", memberName)
148+
}
149+
150+
pod, ok := a.actionCtx.GetCachedStatus().Pod(m.PodName)
151+
if !ok {
152+
a.log.Info().Str("podName", m.PodName).Msg("pod is not present")
153+
return true, nil
154+
}
155+
156+
var op cmpContainer = func(containerSpec core.Container, containerStatus core.Container) error {
157+
topicsLogLevel := map[string]string{}
158+
159+
// Set log levels from the provided spec.
160+
for _, arg := range containerSpec.Command {
161+
if ok, topic, value := getTopicAndLevel(arg); ok {
162+
topicsLogLevel[topic] = value
163+
}
164+
}
165+
166+
if err := a.setLogLevel(ctx, topicsLogLevel); err != nil {
167+
return errors.WithMessage(err, "can not set log level")
168+
}
169+
170+
a.log.Info().Interface("topics", topicsLogLevel).Msg("send log level to the ArangoDB")
171+
return nil
172+
}
173+
174+
if err := checkContainer(member, pod, containerName, op); err != nil && err != api.NotFoundError {
175+
return false, errors.WithMessagef(err, "can not check the container %s", containerName)
176+
}
177+
178+
return true, nil
179+
}
180+
181+
type cmpContainer func(spec core.Container, status core.Container) error
182+
183+
func checkContainer(member *api.ArangoMember, pod *core.Pod, containerName string, action cmpContainer) error {
184+
spec, status, err := validateMemberAndPod(member, pod)
185+
if err != nil {
186+
return err
187+
}
188+
189+
id := getIndexContainer(pod, spec, status, containerName)
190+
if id < 0 {
191+
return api.NotFoundError
192+
}
193+
194+
return action(spec.Spec.Containers[id], status.Spec.Containers[id])
195+
}
196+
197+
// getIndexContainer returns the index of the container from the list of containers.
198+
func getIndexContainer(pod *core.Pod, spec *core.PodTemplateSpec, status *core.PodTemplateSpec,
199+
containerName string) int {
200+
201+
for id := range pod.Spec.Containers {
202+
if pod.Spec.Containers[id].Name == spec.Spec.Containers[id].Name ||
203+
pod.Spec.Containers[id].Name == status.Spec.Containers[id].Name ||
204+
pod.Spec.Containers[id].Name == containerName {
205+
206+
return id
207+
}
208+
}
209+
210+
return -1
211+
}
212+
213+
func validateMemberAndPod(member *api.ArangoMember, pod *core.Pod) (*core.PodTemplateSpec, *core.PodTemplateSpec, error) {
214+
215+
if member.Spec.Template == nil || member.Spec.Template.PodSpec == nil {
216+
return nil, nil, fmt.Errorf("member spec is not present")
217+
}
218+
219+
if member.Status.Template == nil || member.Status.Template.PodSpec == nil {
220+
return nil, nil, fmt.Errorf("member status is not present")
221+
}
222+
223+
if len(pod.Spec.Containers) != len(member.Spec.Template.PodSpec.Spec.Containers) {
224+
return nil, nil, fmt.Errorf("spec container count is not equal")
225+
}
226+
227+
if len(pod.Spec.Containers) != len(member.Status.Template.PodSpec.Spec.Containers) {
228+
return nil, nil, fmt.Errorf("status container count is not equal")
229+
}
230+
231+
return member.Spec.Template.PodSpec, member.Status.Template.PodSpec, nil
232+
}
233+
234+
// CheckProgress returns always true because it does not have to wait for any result.
235+
func (a actionRuntimeContainerArgsUpdate) CheckProgress(_ context.Context) (bool, bool, error) {
236+
return true, false, nil
237+
}
238+
239+
// setLogLevel sets the log's levels for the specific server.
240+
func (a actionRuntimeContainerArgsUpdate) setLogLevel(ctx context.Context, logLevels map[string]string) error {
241+
if len(logLevels) == 0 {
242+
return nil
243+
}
244+
245+
ctxChild, cancel := context.WithTimeout(ctx, arangod.GetRequestTimeout())
246+
defer cancel()
247+
cli, err := a.actionCtx.GetServerClient(ctxChild, a.action.Group, a.action.MemberID)
248+
if err != nil {
249+
return err
250+
}
251+
conn := cli.Connection()
252+
253+
req, err := conn.NewRequest("PUT", "_admin/log/level")
254+
if err != nil {
255+
return err
256+
}
257+
258+
if _, err := req.SetBody(logLevels); err != nil {
259+
return err
260+
}
261+
262+
ctxChild, cancel = context.WithTimeout(ctx, arangod.GetRequestTimeout())
263+
defer cancel()
264+
resp, err := conn.Do(ctxChild, req)
265+
if err != nil {
266+
return err
267+
}
268+
269+
return resp.CheckStatus(200)
270+
}
271+
272+
// getTopicAndLevel returns topics and log level from the argument.
273+
func getTopicAndLevel(arg string) (bool, string, string) {
274+
if !strings.HasPrefix(strings.TrimLeft(arg, " "), "--log.level") {
275+
return false, "", ""
276+
}
277+
278+
logLevelOption := k8sutil.ExtractStringToOptionPair(arg)
279+
if len(logLevelOption.Value) > 0 {
280+
logValueOption := k8sutil.ExtractStringToOptionPair(logLevelOption.Value)
281+
if len(logValueOption.Value) > 0 {
282+
// It is the topic log, e.g.: --log.level=request=INFO.
283+
return true, logValueOption.Key, logValueOption.Value
284+
} else {
285+
// It is the general log, e.g.: --log.level=INFO.
286+
return true, "general", logLevelOption.Value
287+
}
288+
}
289+
290+
return false, "", ""
291+
}

pkg/deployment/reconcile/plan_executor.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (d *Reconciler) executePlan(ctx context.Context, cachedStatus inspectorInte
188188

189189
if err := getActionPost(action, ctx); err != nil {
190190
log.Err(err).Msgf("Post action failed")
191-
return nil, true, errors.WithStack(err)
191+
return nil, false, errors.WithStack(err)
192192
}
193193
} else {
194194
if plan[0].StartTime.IsZero() {
@@ -206,8 +206,7 @@ func (d *Reconciler) executeAction(ctx context.Context, log zerolog.Logger, plan
206206
// Not started yet
207207
ready, err := action.Start(ctx)
208208
if err != nil {
209-
log.Debug().Err(err).
210-
Msg("Failed to start action")
209+
log.Error().Err(err).Msg("Failed to start action")
211210
return false, false, false, errors.WithStack(err)
212211
}
213212

0 commit comments

Comments
 (0)