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

Add support for JSON logging #1467

Merged
merged 1 commit into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion charts/aws-ebs-csi-driver/templates/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ spec:
{{- if .Values.controller.sdkDebugLog }}
- --aws-sdk-debug-log=true
{{- end}}
- --logtostderr
ConnorJC3 marked this conversation as resolved.
Show resolved Hide resolved
{{- with .Values.controller.loggingFormat }}
- --logging-format={{ . }}
{{- end }}
- --v={{ .Values.controller.logLevel }}
{{- range .Values.controller.additionalArgs }}
- {{ . }}
Expand Down
4 changes: 3 additions & 1 deletion charts/aws-ebs-csi-driver/templates/node-windows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ spec:
{{- with .Values.node.volumeAttachLimit }}
- --volume-attach-limit={{ . }}
{{- end }}
- --logtostderr
{{- with .Values.node.loggingFormat }}
- --logging-format={{ . }}
{{- end }}
- --v={{ .Values.node.logLevel }}
env:
- name: CSI_ENDPOINT
Expand Down
4 changes: 3 additions & 1 deletion charts/aws-ebs-csi-driver/templates/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ spec:
{{- with .Values.node.volumeAttachLimit }}
- --volume-attach-limit={{ . }}
{{- end }}
- --logtostderr
{{- with .Values.node.loggingFormat }}
- --logging-format={{ . }}
{{- end }}
- --v={{ .Values.node.logLevel }}
env:
- name: CSI_ENDPOINT
Expand Down
2 changes: 2 additions & 0 deletions charts/aws-ebs-csi-driver/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ fullnameOverride:
controller:
additionalArgs: []
sdkDebugLog: false
loggingFormat: text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not now, but we should consider making JSON the default sometime in the future.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the proposal, they seem to have text as default. Do we want to have this changed prematurely, considering that this will render the divergence between the default formats for core k8s and CSI driver's logs, which may not be a good customer experience?

My feeling is that we should keep the parity with k8s on that. When they change the default setting, it will make sense for us to do the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, we should keep the same default as k8s unless we have a strong reason not to do so.

affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
Expand Down Expand Up @@ -238,6 +239,7 @@ node:
env: []
envFrom: []
kubeletPath: /var/lib/kubelet
loggingFormat: text
logLevel: 2
priorityClassName:
affinity:
Expand Down
19 changes: 15 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,26 @@ limitations under the License.
package main

import (
"flag"
"net/http"

flag "github.com/spf13/pflag"

"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/cloud"
"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver"
logsapi "k8s.io/component-base/logs/api/v1"
json "k8s.io/component-base/logs/json"
"k8s.io/component-base/metrics/legacyregistry"

"k8s.io/klog/v2"
)

func main() {
fs := flag.NewFlagSet("aws-ebs-csi-driver", flag.ExitOnError)

if err := logsapi.RegisterLogFormat(logsapi.JSONLogFormat, json.Factory{}, logsapi.LoggingBetaOptions); err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go with the default logging format, e.g. text, do we still need to register json log format?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to other K8s components such as Kubelet we register supported log formats as early possible and before the flag is set, as this information needs to be known in advance:

https://github.com/kubernetes/component-base/blob/33f62c7b28186a591e1b25e900fe573ed296e181/logs/api/v1/options.go#L186 (names of registered log formats retrieved) -> https://github.com/kubernetes/component-base/blob/33f62c7b28186a591e1b25e900fe573ed296e181/logs/api/v1/options.go#L187 (logging-format flag is added, listing permitted formats).

klog.ErrorS(err, "failed to register JSON log format")
}

options := GetOptions(fs)

cloud.RegisterMetrics()
Expand All @@ -38,7 +46,8 @@ func main() {
go func() {
err := http.ListenAndServe(options.ServerOptions.HttpEndpoint, mux)
if err != nil {
klog.Fatalf("failed to listen & serve metrics from %q: %v", options.ServerOptions.HttpEndpoint, err)
klog.ErrorS(err, "failed to listen & serve metrics", "endpoint", options.ServerOptions.HttpEndpoint)
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
}()
}
Expand All @@ -54,9 +63,11 @@ func main() {
driver.WithWarnOnInvalidTag(options.ControllerOptions.WarnOnInvalidTag),
)
if err != nil {
klog.Fatalln(err)
klog.ErrorS(err, "failed to create driver")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
if err := drv.Run(); err != nil {
klog.Fatalln(err)
klog.ErrorS(err, "failed to run driver")
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
}
36 changes: 29 additions & 7 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ limitations under the License.
package main

import (
"flag"
"fmt"
"os"
"strings"

flag "github.com/spf13/pflag"

"github.com/kubernetes-sigs/aws-ebs-csi-driver/cmd/options"
"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver"

"k8s.io/component-base/featuregate"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
)

Expand All @@ -40,11 +43,14 @@ type Options struct {
// used for testing
var osExit = os.Exit

var featureGate = featuregate.NewFeatureGate()

// GetOptions parses the command line options and returns a struct that contains
// the parsed options.
func GetOptions(fs *flag.FlagSet) *Options {
var (
version = fs.Bool("version", false, "Print the version and exit.")
version = fs.Bool("version", false, "Print the version and exit.")
toStderr = fs.Bool("logtostderr", false, "log to standard error instead of files. DEPRECATED: will be removed in a future release.")

args = os.Args[1:]
mode = driver.AllMode
Expand All @@ -55,7 +61,15 @@ func GetOptions(fs *flag.FlagSet) *Options {
)

serverOptions.AddFlags(fs)
klog.InitFlags(fs)

c := logsapi.NewLoggingConfiguration()

err := logsapi.AddFeatureGates(featureGate)
if err != nil {
klog.ErrorS(err, "failed to add feature gates")
}

logsapi.AddFlags(c, fs)

if len(os.Args) > 1 {
cmd := os.Args[1]
Expand Down Expand Up @@ -87,17 +101,25 @@ func GetOptions(fs *flag.FlagSet) *Options {
}
}

if err := fs.Parse(args); err != nil {
if err = fs.Parse(args); err != nil {
panic(err)
}

err = logsapi.ValidateAndApply(c, featureGate)
if err != nil {
klog.ErrorS(err, "failed to validate and apply logging configuration")
}

if *version {
info, err := driver.GetVersionJSON()
if err != nil {
klog.Fatalln(err)
klog.ErrorS(err, "failed to get version", "version", info)
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
fmt.Println(info)
osExit(0)
}

if *toStderr {
klog.SetOutput(os.Stderr)
}

return &Options{
Expand Down
2 changes: 1 addition & 1 deletion cmd/options/controller_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
package options

import (
"flag"
flag "github.com/spf13/pflag"

cliflag "k8s.io/component-base/cli/flag"
)
Expand Down
3 changes: 2 additions & 1 deletion cmd/options/controller_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ limitations under the License.
package options

import (
"flag"
"testing"

flag "github.com/spf13/pflag"
)

func TestControllerOptions(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/options/node_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
package options

import (
"flag"
flag "github.com/spf13/pflag"
)

// NodeOptions contains options and configuration settings for the node service.
Expand Down
3 changes: 2 additions & 1 deletion cmd/options/node_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ limitations under the License.
package options

import (
"flag"
"testing"

flag "github.com/spf13/pflag"
)

func TestNodeOptions(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/options/server_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
package options

import (
"flag"
flag "github.com/spf13/pflag"

"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver"
)
Expand Down
3 changes: 2 additions & 1 deletion cmd/options/server_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ limitations under the License.
package options

import (
"flag"
"testing"

flag "github.com/spf13/pflag"
)

func TestServerOptions(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions cmd/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ limitations under the License.
package main

import (
"flag"
"os"
"reflect"
"strconv"
"testing"

flag "github.com/spf13/pflag"

"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver"
)

Expand Down Expand Up @@ -56,20 +57,19 @@ func TestGetOptions(t *testing.T) {
}, additionalArgs...)

if withServerOptions {
args = append(args, "-"+endpointFlagName+"="+endpoint)
args = append(args, "--"+endpointFlagName+"="+endpoint)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the previous versions not work anymore? Do we care about this theoretically breaking change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To provide some context, this change was introduced as a result of migrating to the pflag package which is a "drop-in replacement for Go's flag package". This flag type is used to add the component-based flags: https://pkg.go.dev/k8s.io/component-base/logs/api/v1#AddFlags.

Unlike Go's standard flag package, when using the pflag package, a single dash before an option means something different than a double dash. Single dashes signify a series of shorthand letters for flags. Hence this change.

The new behavior for using the "shorthand flags" - is as follows:

$ ./aws-ebs-csi-driver -aws-sdk-debug-log=true

unknown shorthand flag: 'a' in -aws-sdk-debug-log=true

It is common convention to use a single hyphen - to specify a single character flag, and a double hyphen -- to specify a keyword flag so I don't believe this change is particularly dangerous but if it is a concern we could theoretically support shorthand flags as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an acceptable justification, but we MUST make sure to call this out in the changelog.

}
if withControllerOptions {
args = append(args, "-"+extraTagsFlagName+"="+extraTagKey+"="+extraTagValue)
args = append(args, "-"+awsSdkDebugFlagName+"="+strconv.FormatBool(awsSdkDebugFlagValue))
args = append(args, "--"+extraTagsFlagName+"="+extraTagKey+"="+extraTagValue)
args = append(args, "--"+awsSdkDebugFlagName+"="+strconv.FormatBool(awsSdkDebugFlagValue))
}
if withNodeOptions {
args = append(args, "-"+VolumeAttachLimitFlagName+"="+strconv.FormatInt(VolumeAttachLimit, 10))
args = append(args, "--"+VolumeAttachLimitFlagName+"="+strconv.FormatInt(VolumeAttachLimit, 10))
}

oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = args

options := GetOptions(flagSet)

if withServerOptions {
Expand Down Expand Up @@ -172,7 +172,7 @@ func TestGetOptions(t *testing.T) {
defer func() { os.Args = oldArgs }()
os.Args = []string{
"aws-ebs-csi-driver",
"-version",
"--version",
}

flagSet := flag.NewFlagSet("test-flagset", flag.ContinueOnError)
Expand Down
2 changes: 1 addition & 1 deletion deploy/kubernetes/base/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ spec:
args:
# - {all,controller,node} # specify the driver mode
- --endpoint=$(CSI_ENDPOINT)
- --logtostderr
- --logging-format=text
- --v=2
env:
- name: CSI_ENDPOINT
Expand Down
2 changes: 1 addition & 1 deletion deploy/kubernetes/base/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ spec:
args:
- node
- --endpoint=$(CSI_ENDPOINT)
- --logtostderr
- --logging-format=text
- --v=2
env:
- name: CSI_ENDPOINT
Expand Down
1 change: 1 addition & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ There are a couple of driver options that can be passed as arguments when starti
| extra-tags | key1=value1,key2=value2 | | Tags attached to each dynamically provisioned resource|
| k8s-tag-cluster-id | aws-cluster-id-1 | | ID of the Kubernetes cluster used for tagging provisioned EBS volumes|
| aws-sdk-debug-log | true | false | If set to true, the driver will enable the aws sdk debug log level|
| logging-format | json | text | Sets the log format. Permitted formats: text, json|
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
github.com/onsi/ginkgo/v2 v2.4.0
github.com/onsi/gomega v1.24.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
golang.org/x/sys v0.3.0
google.golang.org/grpc v1.51.0
Expand Down Expand Up @@ -40,6 +41,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
Expand Down Expand Up @@ -73,7 +75,6 @@ require (
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect
go.opentelemetry.io/otel v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect
Expand All @@ -83,6 +84,9 @@ require (
go.opentelemetry.io/otel/sdk v1.10.0 // indirect
go.opentelemetry.io/otel/trace v1.10.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.44.160 h1:F41sWUel1CJ69ezoBGCg8sDyu9kyeKEpwmDrLXbCuyA=
github.com/aws/aws-sdk-go v1.44.160/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -130,6 +132,8 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
Expand Down Expand Up @@ -418,7 +422,14 @@ go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/A
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down Expand Up @@ -628,6 +639,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down
Loading