Skip to content

Commit

Permalink
[Feature] Scheduler CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
ajanikow committed Mar 25, 2024
1 parent 612699c commit b715564
Show file tree
Hide file tree
Showing 9 changed files with 817 additions and 37 deletions.
48 changes: 19 additions & 29 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ const (
defaultServerPort = 8528
defaultAPIHTTPPort = 8628
defaultAPIGRPCPort = 8728
defaultLogLevel = "info"
defaultAdminSecretName = "arangodb-operator-dashboard"
defaultAPIJWTSecretName = "arangodb-operator-api-jwt"
defaultAPIJWTKeySecretName = "arangodb-operator-api-jwt-key"
Expand All @@ -96,9 +95,6 @@ var (
hardLimit uint64
}

logFormat string
logLevels []string
logSampling bool
serverOptions struct {
host string
port int
Expand Down Expand Up @@ -195,9 +191,6 @@ func init() {
f.StringVar(&serverOptions.tlsSecretName, "server.tls-secret-name", "", "Name of secret containing tls.crt & tls.key for HTTPS server (if empty, self-signed certificate is used)")
f.StringVar(&serverOptions.adminSecretName, "server.admin-secret-name", defaultAdminSecretName, "Name of secret containing username + password for login to the dashboard")
f.BoolVar(&serverOptions.allowAnonymous, "server.allow-anonymous-access", false, "Allow anonymous access to the dashboard")
f.StringVar(&logFormat, "log.format", "pretty", "Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used")
f.StringArrayVar(&logLevels, "log.level", []string{defaultLogLevel}, fmt.Sprintf("Set log levels in format <level> or <logger>=<level>. Possible loggers: %s", strings.Join(logging.Global().Names(), ", ")))
f.BoolVar(&logSampling, "log.sampling", true, "If true, operator will try to minimize duplication of logging events")
f.BoolVar(&apiOptions.enabled, "api.enabled", true, "Enable operator HTTP and gRPC API")
f.IntVar(&apiOptions.httpPort, "api.http-port", defaultAPIHTTPPort, "HTTP API port to listen on")
f.IntVar(&apiOptions.grpcPort, "api.grpc-port", defaultAPIGRPCPort, "gRPC API port to listen on")
Expand Down Expand Up @@ -247,6 +240,9 @@ func init() {
f.StringArrayVar(&metricsOptions.excludedMetricPrefixes, "metrics.excluded-prefixes", nil, "List of the excluded metrics prefixes")
f.BoolVar(&operatorImageDiscovery.defaultStatusDiscovery, "image.discovery.status", true, "Discover Operator Image from Pod Status by default. When disabled Pod Spec is used.")
f.DurationVar(&operatorImageDiscovery.timeout, "image.discovery.timeout", time.Minute, "Timeout for image discovery process")
if err := logging.Init(&cmdMain); err != nil {
panic(err.Error())
}
if err := features.Init(&cmdMain); err != nil {
panic(err.Error())
}
Expand Down Expand Up @@ -308,24 +304,10 @@ func executeMain(cmd *cobra.Command, args []string) {
kclient.SetDefaultBurst(operatorKubernetesOptions.burst)

// Prepare log service
var err error

levels, err := logging.ParseLogLevelsFromArgs(logLevels)
if err != nil {
logger.Err(err).Fatal("Unable to parse log level")
if err := logging.Enable(); err != nil {
logger.Err(err).Fatal("Unable to enable logger")
}

// Set root logger to stdout (JSON formatted) if not prettified
if strings.ToUpper(logFormat) == "JSON" {
logging.Global().SetRoot(zerolog.New(os.Stdout).With().Timestamp().Logger())
} else if strings.ToLower(logFormat) != "pretty" && logFormat != "" {
logger.Fatal("Unknown log format: %s", logFormat)
}
logging.Global().Configure(logging.Config{
Levels: levels,
Sampling: logSampling,
})

podNameParts := strings.Split(name, "-")
operatorID := podNameParts[len(podNameParts)-1]

Expand All @@ -347,16 +329,16 @@ func executeMain(cmd *cobra.Command, args []string) {
!operatorOptions.enableBackup && !operatorOptions.enableApps && !operatorOptions.enableK2KClusterSync && !operatorOptions.enableML {
if !operatorOptions.versionOnly {
if version.GetVersionV1().IsEnterprise() {
logger.Err(err).Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml or any combination of these")
logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml or any combination of these")
} else {
logger.Err(err).Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync or any combination of these")
logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync or any combination of these")
}
}
} else if operatorOptions.versionOnly {
logger.Err(err).Fatal("Options --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml cannot be enabled together with --operator.version")
logger.Fatal("Options --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml cannot be enabled together with --operator.version")
} else if !version.GetVersionV1().IsEnterprise() {
if operatorOptions.enableML {
logger.Err(err).Fatal("Options --operator.ml can be enabled only on the Enterprise Operator")
logger.Fatal("Options --operator.ml can be enabled only on the Enterprise Operator")
}
}

Expand Down Expand Up @@ -444,7 +426,11 @@ func executeMain(cmd *cobra.Command, args []string) {
if err != nil {
logger.Err(err).Fatal("Failed to create API server")
}
go errors.LogError(logger, "while running API server", apiServer.Run)
go func() {
if err := apiServer.Run(); err != nil {
logger.Err(err).Error("while running API server")
}
}()
}

listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
Expand Down Expand Up @@ -493,7 +479,11 @@ func executeMain(cmd *cobra.Command, args []string) {
}); err != nil {
logger.Err(err).Fatal("Failed to create HTTP server")
} else {
go errors.LogError(logger, "error while starting server", svr.Run)
go func() {
if err := svr.Run(); err != nil {
logger.Err(err).Error("error while starting server")
}
}()
}

// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
Expand Down
39 changes: 39 additions & 0 deletions cmd/scheduler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package cmd

import (
"github.com/spf13/cobra"

"github.com/arangodb/kube-arangodb/pkg/scheduler"
)

func init() {
cmd := &cobra.Command{
Use: "scheduler",
}

if err := scheduler.InitCommand(cmd); err != nil {
panic(err.Error())
}

cmdMain.AddCommand(cmd)
}
85 changes: 85 additions & 0 deletions pkg/logging/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package logging

import (
"fmt"
"os"
"strings"
"sync"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

"github.com/arangodb/kube-arangodb/pkg/util/errors"
)

const (
defaultLogLevel = "info"
)

var (
enableLock sync.Mutex
enabled bool

cli struct {
format string
levels []string
sampling bool
}
)

func Init(cmd *cobra.Command) error {
f := cmd.PersistentFlags()

f.StringVar(&cli.format, "log.format", "pretty", "Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used")
f.StringArrayVar(&cli.levels, "log.level", []string{defaultLogLevel}, fmt.Sprintf("Set log levels in format <level> or <logger>=<level>. Possible loggers: %s", strings.Join(Global().Names(), ", ")))
f.BoolVar(&cli.sampling, "log.sampling", true, "If true, operator will try to minimize duplication of logging events")

return nil
}

func Enable() error {
enableLock.Lock()
defer enableLock.Unlock()

if enabled {
return errors.Errorf("Logger already enabled")
}

levels, err := ParseLogLevelsFromArgs(cli.levels)
if err != nil {
return errors.WithMessagef(err, "Unable to parse levels")
}

// Set root logger to stdout (JSON formatted) if not prettified
if strings.ToUpper(cli.format) == "JSON" {
Global().SetRoot(zerolog.New(os.Stdout).With().Timestamp().Logger())
} else if strings.ToLower(cli.format) != "pretty" && cli.format != "" {
return errors.Errorf("Unknown log format: %s", cli.format)
}
Global().Configure(Config{
Levels: levels,
Sampling: cli.sampling,
})

return nil
}
150 changes: 150 additions & 0 deletions pkg/scheduler/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package scheduler

import (
"context"
"os"
"strings"

"github.com/spf13/cobra"
"sigs.k8s.io/yaml"

"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
)

func InitCommand(cmd *cobra.Command) error {
var c cli
return c.register(cmd)
}

type cli struct {
Namespace string

Labels []string
Envs []string

Profiles []string

Container string

Image string
}

func (c *cli) asRequest(args ...string) (Request, error) {
var r = Request{
Labels: map[string]string{},
Envs: map[string]string{},
}

for _, l := range c.Labels {
p := strings.SplitN(l, "=", 2)
if len(p) == 1 {
r.Labels[p[0]] = ""
logger.Debug("Label Discovered: %s", p[0])
} else {
r.Labels[p[0]] = p[1]
logger.Debug("Label Discovered: %s=%s", p[0], p[1])
}
}

for _, l := range c.Envs {
p := strings.SplitN(l, "=", 2)
if len(p) == 1 {
return r, errors.Errorf("Missing value for env: %s", p[0])
} else {
r.Envs[p[0]] = p[1]
logger.Debug("Env Discovered: %s=%s", p[0], p[1])
}
}

if len(c.Profiles) > 0 {
r.Profiles = c.Profiles
logger.Debug("Enabling profiles: %s", strings.Join(c.Profiles, ", "))
}

r.Container = util.NewType(c.Container)
if c.Image != "" {
r.Image = util.NewType(c.Image)
}

r.Args = args

return r, nil
}

func (c *cli) register(cmd *cobra.Command) error {
if err := logging.Init(cmd); err != nil {
return err
}

cmd.RunE = c.run

f := cmd.PersistentFlags()

f.StringVarP(&c.Namespace, "namespace", "n", constants.NamespaceWithDefault("default"), "Kubernetes namespace")
f.StringSliceVarP(&c.Labels, "label", "l", nil, "Scheduler Render Labels in format <key>=<value>")
f.StringSliceVarP(&c.Envs, "env", "e", nil, "Scheduler Render Envs in format <key>=<value>")
f.StringSliceVarP(&c.Profiles, "profile", "p", nil, "Scheduler Render Profiles")
f.StringVar(&c.Container, "container", DefaultContainerName, "Container Name")
f.StringVar(&c.Image, "image", "", "Image")

return nil
}

func (c *cli) run(cmd *cobra.Command, args []string) error {
if err := logging.Enable(); err != nil {
return err
}

r, err := c.asRequest()
if err != nil {
return err
}

k, ok := kclient.GetDefaultFactory().Client()
if !ok {
return errors.Errorf("Unable to create Kubernetes Client")
}

s := NewScheduler(k, c.Namespace)

rendered, profiles, err := s.Render(context.Background(), r)
if err != nil {
return err
}
logger.Debug("Enabled profiles: %s", strings.Join(profiles, ", "))

data, err := yaml.Marshal(rendered)
if err != nil {
return err
}

if _, err := util.WriteAll(os.Stdout, data); err != nil {
return err
}

return nil
}
Loading

0 comments on commit b715564

Please sign in to comment.