<commit_before>// Copyright (c) 2012-2014 José Carlos Nieto, https://menteslibres.net/xiam
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package page

import (
	"html/template"
	"os"
	"path"
	"regexp"
	"sort"
	"strings"
)

// Page struct holds information on the current document being served.
type Page struct {

	// Page's title, this is guessed from the current document. (Looks for the
	// first H1, H2, ..., H6 tag)
	Title string

	// The HTML of the current document.
	Content template.HTML

	// The HTML of the _header.md or _header.html file on the current document's
	// directory.
	ContentHeader template.HTML

	// The HTML of the _footer.md or _footer.html file on the current document's
	// directory.
	ContentFooter template.HTML

	// An array of maps that contains names and links of all the items on the
	// document root.  Names begginning with "." or "_" are ignored in this list.
	Menu []map[string]interface{}

	// An array of maps that contains names and links of all the items on the
	// current document's directory.  Names begginning with "." or "_" are
	// ignored in this list.
	SideMenu []map[string]interface{}

	// An array of maps that contains names and links of the current document's
	// path.
	BreadCrumb []map[string]interface{}

	// A map that contains name and link of the current page.
	CurrentPage map[string]interface{}

	// Absolute path of the current document.
	FilePath string

	// Absolute parent directory of the current document.
	FileDir string

	// Relative path of the current document.
	BasePath string

	// Relative parent directory of the current document.
	BaseDir string

	// True if the current document is / (home).
	IsHome bool
}

// List of extensions to look for. Elements on the left have precedence.
var extensions = []string{".html", ".md", ""}

// fileList struct is a sorted list of files.
type fileList []os.FileInfo

func (f fileList) Len() int {
	return len(f)
}

func (f fileList) Less(i, j int) bool {
	return f[i].Name() < f[j].Name()
}

func (f fileList) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

type byName struct{ fileList }

const (
	pathSeparator = string(os.PathSeparator)
)

// removeKnownExtension strips out known extensions from a given file name.
func removeKnownExtension(s string) string {
	fileExt := path.Ext(s)

	for _, ext := range extensions {
		if ext != "" {
			if fileExt == ext {
				return s[:len(s)-len(ext)]
			}
		}
	}

	return s
}

// filterList returns files in a directory passed through a filter.
func filterList(directory string, filter func(os.FileInfo) bool) fileList {
	var list fileList
	var err error

	// Attempt to open directory.
	var fp *os.File
	if fp, err = os.Open(directory); err != nil {
		panic(err)
	}

	defer fp.Close()

	// Listing directory contents.
	var dirContents []os.FileInfo
	if dirContents, err = fp.Readdir(-1); err != nil {
		panic(err)
	}

	// Looping over directory contents.
	for _, file := range dirContents {
		if filter(file) == true {
			list = append(list, file)
		}
	}

	// Sorting file list.
	sort.Sort(byName{list})

	return list
}

// dummyFilter is a filter for filterList. Returns all files except for those
// that begin with "." or "_".
func dummyFilter(f os.FileInfo) bool {
	if strings.HasPrefix(f.Name(), ".") == false && strings.HasPrefix(f.Name(), "_") == false {
		return true
	}
	return false
}

// directoryFilter is a filter for filterList. Returns all directories except
// those that begin with "." or "_".
func directoryFilter(f os.FileInfo) bool {
	if strings.HasPrefix(f.Name(), ".") == false && strings.HasPrefix(f.Name(), "_") == false {
		return f.IsDir()
	}
	return false
}

// fileFilter is a filter for filterList. Returns all files except for those
// that begin with "." or "_".
func fileFilter(f os.FileInfo) bool {
	if strings.HasPrefix(f.Name(), ".") == false && strings.HasPrefix(f.Name(), "_") == false {
		return (f.IsDir() == false)
	}
	return false
}

// createTitle returns a stylized human title, given a file name.
func createTitle(s string) string {
	s = removeKnownExtension(s)

	re, _ := regexp.Compile("[-_]")
	s = re.ReplaceAllString(s, " ")

	return strings.Title(s[:1]) + s[1:]
}

// CreateLink returns a link to another page.
func (p *Page) CreateLink(file os.FileInfo, prefix string) map[string]interface{} {
	item := map[string]interface{}{}

	if file.IsDir() == true {
		item["link"] = prefix + file.Name() + "/"
	} else {
		item["link"] = prefix + removeKnownExtension(file.Name())
	}

	item["text"] = createTitle(file.Name())

	return item
}

// CreateMenu scans files and directories and creates a menu.
func (p *Page) CreateMenu() {
	var item map[string]interface{}
	p.Menu = []map[string]interface{}{}

	files := filterList(p.FileDir, directoryFilter)

	for _, file := range files {
		item = p.CreateLink(file, p.BasePath)
		children := filterList(p.FileDir+pathSeparator+file.Name(), directoryFilter)
		if len(children) > 0 {
			item["children"] = []map[string]interface{}{}
			for _, child := range children {
				childItem := p.CreateLink(child, p.BasePath+file.Name()+"/")
				item["children"] = append(item["children"].([]map[string]interface{}), childItem)
			}
		}
		p.Menu = append(p.Menu, item)
	}
}

// CreateBreadCrumb populates Page.BreadCrumb with links.
func (p *Page) CreateBreadCrumb() {

	p.BreadCrumb = []map[string]interface{}{
		map[string]interface{}{
			"link": "/",
			"text": "Home",
		},
	}

	chunks := strings.Split(strings.Trim(p.BasePath, "/"), "/")

	prefix := ""

	for _, chunk := range chunks {
		if chunk != "" {
			item := map[string]interface{}{}
			item["link"] = prefix + "/" + chunk + "/"
			item["text"] = createTitle(chunk)
			prefix = prefix + pathSeparator + chunk
			p.BreadCrumb = append(p.BreadCrumb, item)
			p.CurrentPage = item
		}
	}

}

// CreateSideMenu populates Page.SideMenu with files on the current document's
// directory.
func (p *Page) CreateSideMenu() {
	var item map[string]interface{}
	p.SideMenu = []map[string]interface{}{}

	files := filterList(p.FileDir, dummyFilter)

	for _, file := range files {
		item = p.CreateLink(file, p.BasePath)
		if strings.ToLower(item["text"].(string)) != "index" {
			p.SideMenu = append(p.SideMenu, item)
		}
	}
}
<commit_msg>Coding style review.<commit_after>// Copyright (c) 2012-2014 José Carlos Nieto, https://menteslibres.net/xiam
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package page

import (
	"html/template"
	"os"
	"path"
	"regexp"
	"sort"
	"strings"
)

// Page struct holds information on the current document being served.
type Page struct {

	// Title of the page. This is guessed from the current document. (It looks
	// for the first H1, H2, ..., H6 tag).
	Title string

	// The HTML source of the current document.
	Content template.HTML

	// The HTML source of the _header.md or _header.html file on the current
	// document's directory.
	ContentHeader template.HTML

	// The HTML source of the _footer.md or _footer.html file on the current
	// document's directory.
	ContentFooter template.HTML

	// An array of maps that contains names and links of all the items on the
	// document root.  Names that begin with "." or "_" are ignored in this list.
	Menu []map[string]interface{}

	// An array of maps that contains names and links of all the items on the
	// current document's directory.  Names that begin with "." or "_" are
	// ignored in this list.
	SideMenu []map[string]interface{}

	// An array of maps that contains names and links of the current document's
	// path.
	BreadCrumb []map[string]interface{}

	// A map that contains the name and link of the current page.
	CurrentPage map[string]interface{}

	// Absolute path of the current document.
	FilePath string

	// Absolute parent directory of the current document.
	FileDir string

	// Relative path of the current document.
	BasePath string

	// Relative parent directory of the current document.
	BaseDir string

	// True if the current document is / (home).
	IsHome bool
}

const (
	pathSeparator = string(os.PathSeparator)
)

// List of known extensions.
var knownExtensions = []string{".html", ".md", ""}

// fileList struct is a sorted list of files.
type fileList []os.FileInfo

func (f fileList) Len() int {
	return len(f)
}

func (f fileList) Less(i, j int) bool {
	return f[i].Name() < f[j].Name()
}

func (f fileList) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

// removeKnownExtension strips out known extensions from a given file name.
func removeKnownExtension(s string) string {
	fileExt := path.Ext(s)

	for _, ext := range knownExtensions {
		if ext != "" {
			if fileExt == ext {
				return s[:len(s)-len(ext)]
			}
		}
	}

	return s
}

// filterList returns files in a directory passed through a filter.
func filterList(directory string, filter func(os.FileInfo) bool) fileList {
	var list fileList
	var err error

	// Attempt to open directory.
	var fp *os.File
	if fp, err = os.Open(directory); err != nil {
		panic(err)
	}

	defer fp.Close()

	// Listing directory contents.
	var dirContents []os.FileInfo
	if dirContents, err = fp.Readdir(-1); err != nil {
		panic(err)
	}

	// Looping over directory contents.
	for _, file := range dirContents {
		if filter(file) == true {
			list = append(list, file)
		}
	}

	// Sorting file list.
	sort.Sort(struct{ fileList }{list})

	return list
}

// dummyFilter is a filter for filterList. Returns all files except for those
// that begin with "." or "_".
func dummyFilter(f os.FileInfo) bool {
	if !strings.HasPrefix(f.Name(), ".") && !strings.HasPrefix(f.Name(), "_") {
		return true
	}
	return false
}

// directoryFilter is a filter for filterList. Returns all directories except
// those that begin with "." or "_".
func directoryFilter(f os.FileInfo) bool {
	if !strings.HasPrefix(f.Name(), ".") && !strings.HasPrefix(f.Name(), "_") {
		return f.IsDir()
	}
	return false
}

// fileFilter is a filter for filterList. Returns all files except for those
// that begin with "." or "_".
func fileFilter(f os.FileInfo) bool {
	if !strings.HasPrefix(f.Name(), ".") && !strings.HasPrefix(f.Name(), "_") {
		return (f.IsDir() == false)
	}
	return false
}

// createTitle expects a filename and returns a stylized human title.
func createTitle(s string) string {
	s = removeKnownExtension(s)
	s = regexp.MustCompile("[-_]").ReplaceAllString(s, " ")
	return strings.Title(s[:1]) + s[1:]
}

// CreateLink returns a link to another page.
func (p *Page) CreateLink(file os.FileInfo, prefix string) map[string]interface{} {
	item := map[string]interface{}{}

	if file.IsDir() == true {
		item["link"] = prefix + file.Name() + "/"
	} else {
		item["link"] = prefix + removeKnownExtension(file.Name())
	}

	item["text"] = createTitle(file.Name())

	return item
}

// CreateMenu scans files and directories and builds a list of children links.
func (p *Page) CreateMenu() {
	var item map[string]interface{}
	p.Menu = []map[string]interface{}{}

	files := filterList(p.FileDir, directoryFilter)

	for _, file := range files {
		item = p.CreateLink(file, p.BasePath)
		children := filterList(p.FileDir+pathSeparator+file.Name(), directoryFilter)
		if len(children) > 0 {
			item["children"] = []map[string]interface{}{}
			for _, child := range children {
				childItem := p.CreateLink(child, p.BasePath+file.Name()+"/")
				item["children"] = append(item["children"].([]map[string]interface{}), childItem)
			}
		}
		p.Menu = append(p.Menu, item)
	}
}

// CreateBreadCrumb populates Page.BreadCrumb with links.
func (p *Page) CreateBreadCrumb() {

	p.BreadCrumb = []map[string]interface{}{
		map[string]interface{}{
			"link": "/",
			"text": "Home",
		},
	}

	chunks := strings.Split(strings.Trim(p.BasePath, "/"), "/")

	prefix := ""

	for _, chunk := range chunks {
		if chunk != "" {
			item := map[string]interface{}{}
			item["link"] = prefix + "/" + chunk + "/"
			item["text"] = createTitle(chunk)
			prefix = prefix + pathSeparator + chunk
			p.BreadCrumb = append(p.BreadCrumb, item)
			p.CurrentPage = item
		}
	}

}

// CreateSideMenu populates Page.SideMenu with files on the current document's
// directory.
func (p *Page) CreateSideMenu() {
	var item map[string]interface{}
	p.SideMenu = []map[string]interface{}{}

	files := filterList(p.FileDir, dummyFilter)

	for _, file := range files {
		item = p.CreateLink(file, p.BasePath)
		if strings.ToLower(item["text"].(string)) != "index" {
			p.SideMenu = append(p.SideMenu, item)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2018-2019 Authors of Cilium
//
// 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.

package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/k8s"
	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned"
	k8sversion "github.com/cilium/cilium/pkg/k8s/version"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/version"

	gops "github.com/google/gops/agent"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"k8s.io/klog"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "cilium-operator")

	rootCmd = &cobra.Command{
		Use:   "cilium-operator",
		Short: "Run the cilium-operator",
		Run: func(cmd *cobra.Command, args []string) {
			runOperator(cmd)
		},
	}

	k8sAPIServer        string
	k8sKubeConfigPath   string
	kvStore             string
	kvStoreOpts         = make(map[string]string)
	apiServerPort       uint16
	shutdownSignal      = make(chan struct{})
	synchronizeServices bool
	enableCepGC         bool

	ciliumK8sClient clientset.Interface
)

func main() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-signals
		close(shutdownSignal)
	}()

	// Open socket for using gops to get stacktraces of the agent.
	if err := gops.Listen(gops.Options{}); err != nil {
		errorString := fmt.Sprintf("unable to start gops: %s", err)
		fmt.Println(errorString)
		os.Exit(-1)
	}

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)

	flags := rootCmd.Flags()
	flags.Bool("version", false, "Print version information")
	flags.Int(option.ClusterIDName, 0, "Unique identifier of the cluster")
	option.BindEnv(option.ClusterIDName)
	flags.String(option.ClusterName, defaults.ClusterName, "Name of the cluster")
	option.BindEnv(option.ClusterName)
	flags.BoolP("debug", "D", false, "Enable debugging mode")
	flags.StringVar(&k8sAPIServer, "k8s-api-server", "", "Kubernetes api address server (for https use --k8s-kubeconfig-path instead)")
	flags.StringVar(&k8sKubeConfigPath, "k8s-kubeconfig-path", "", "Absolute path of the kubernetes kubeconfig file")
	flags.StringVar(&kvStore, "kvstore", "", "Key-value store type")
	flags.Var(option.NewNamedMapOptions("kvstore-opts", &kvStoreOpts, nil), "kvstore-opt", "Key-value store options")
	flags.Uint16Var(&apiServerPort, "api-server-port", 9234, "Port on which the operator should serve API requests")

	flags.BoolVar(&synchronizeServices, "synchronize-k8s-services", true, "Synchronize Kubernetes services to kvstore")
	flags.BoolVar(&enableCepGC, "cilium-endpoint-gc", true, "Enable CiliumEndpoint garbage collector")
	flags.DurationVar(&identityGCInterval, "identity-gc-interval", time.Minute*10, "GC interval for security identities")
	flags.DurationVar(&kvNodeGCInterval, "nodes-gc-interval", time.Minute*2, "GC interval for nodes store in the kvstore")

	flags.IntVar(&unmanagedKubeDnsWatcherInterval, "unmanaged-pod-watcher-interval", 15, "Interval to check for unmanaged kube-dns pods (0 to disable)")

	// We need to obtain from Cilium ConfigMap if the CiliumEndpointCRD option
	// is enabled or disabled. This option is marked as hidden because the
	// Cilium Endpoint CRD controller is not in this program and by having it
	// being printed by operator --help could confuse users.
	flags.Bool(option.DisableCiliumEndpointCRDName, false, "")
	flags.MarkHidden(option.DisableCiliumEndpointCRDName)
	option.BindEnv(option.DisableCiliumEndpointCRDName)

	viper.BindPFlags(flags)

	// Make sure that klog logging variables are initialized so that we can
	// update them from this file.
	klog.InitFlags(nil)

	// Make sure klog (used by the client-go dependency) logs to stderr, as it
	// will try to log to directories that may not exist in the cilium-operator
	// container (/tmp) and cause the cilium-operator to exit.
	flag.Set("logtostderr", "true")
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if viper.GetBool("version") {
		fmt.Printf("Cilium %s\n", version.Version)
		os.Exit(0)
	}

	option.Config.ClusterName = viper.GetString(option.ClusterName)
	option.Config.ClusterID = viper.GetInt(option.ClusterIDName)
	option.Config.DisableCiliumEndpointCRD = viper.GetBool(option.DisableCiliumEndpointCRDName)

	viper.SetEnvPrefix("cilium")
	viper.SetConfigName("cilium-operator")
}

func runOperator(cmd *cobra.Command) {
	logging.SetupLogging([]string{}, map[string]string{}, "cilium-operator", viper.GetBool("debug"))

	log.Infof("Cilium Operator %s", version.Version)

	if err := kvstore.Setup(kvStore, kvStoreOpts, nil); err != nil {
		log.WithError(err).WithFields(logrus.Fields{
			"kvstore": kvStore,
			"address": kvStoreOpts[fmt.Sprintf("%s.address", kvStore)],
		}).Fatal("Unable to setup kvstore")
	}

	k8s.Configure(k8sAPIServer, k8sKubeConfigPath)
	if err := k8s.Init(); err != nil {
		log.WithError(err).Fatal("Unable to connect to Kubernetes apiserver")
	}

	ciliumK8sClient = k8s.CiliumClient()
	k8sversion.Update(k8s.Client())
	if !k8sversion.Capabilities().MinimalVersionMet {
		log.Fatalf("Minimal kubernetes version not met: %s < %s",
			k8sversion.Version(), k8sversion.MinimalVersionConstraint)
	}

	if synchronizeServices {
		startSynchronizingServices()
	}

	if enableCepGC {
		enableCiliumEndpointSyncGC()
	}

	if err := runNodeWatcher(); err != nil {
		log.WithError(err).Error("Unable to setup node watcher")
	}

	if identityGCInterval != time.Duration(0) {
		startIdentityGC()
	}

	if option.Config.DisableCiliumEndpointCRD {
		log.Infof("KubeDNS unmanaged pods controller disabled as %q option is set to 'disabled' in Cilium ConfigMap", option.DisableCiliumEndpointCRDName)
	} else {
		enableUnmanagedKubeDNSController()
	}

	err := enableCNPWatcher()
	if err != nil {
		log.WithError(err).WithField("subsys", "CNPWatcher").Fatal(
			"Cannot connect to Kubernetes apiserver ")
	}

	go StartServer(fmt.Sprintf(":%d", apiServerPort), shutdownSignal)

	<-shutdownSignal
	// graceful exit
	log.Info("Received termination signal. Shutting down")
	return
}
<commit_msg>operator: Start health API earlier<commit_after>// Copyright 2018-2019 Authors of Cilium
//
// 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.

package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/k8s"
	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned"
	k8sversion "github.com/cilium/cilium/pkg/k8s/version"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/version"

	gops "github.com/google/gops/agent"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"k8s.io/klog"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "cilium-operator")

	rootCmd = &cobra.Command{
		Use:   "cilium-operator",
		Short: "Run the cilium-operator",
		Run: func(cmd *cobra.Command, args []string) {
			runOperator(cmd)
		},
	}

	k8sAPIServer        string
	k8sKubeConfigPath   string
	kvStore             string
	kvStoreOpts         = make(map[string]string)
	apiServerPort       uint16
	shutdownSignal      = make(chan struct{})
	synchronizeServices bool
	enableCepGC         bool

	ciliumK8sClient clientset.Interface
)

func main() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-signals
		close(shutdownSignal)
	}()

	// Open socket for using gops to get stacktraces of the agent.
	if err := gops.Listen(gops.Options{}); err != nil {
		errorString := fmt.Sprintf("unable to start gops: %s", err)
		fmt.Println(errorString)
		os.Exit(-1)
	}

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)

	flags := rootCmd.Flags()
	flags.Bool("version", false, "Print version information")
	flags.Int(option.ClusterIDName, 0, "Unique identifier of the cluster")
	option.BindEnv(option.ClusterIDName)
	flags.String(option.ClusterName, defaults.ClusterName, "Name of the cluster")
	option.BindEnv(option.ClusterName)
	flags.BoolP("debug", "D", false, "Enable debugging mode")
	flags.StringVar(&k8sAPIServer, "k8s-api-server", "", "Kubernetes api address server (for https use --k8s-kubeconfig-path instead)")
	flags.StringVar(&k8sKubeConfigPath, "k8s-kubeconfig-path", "", "Absolute path of the kubernetes kubeconfig file")
	flags.StringVar(&kvStore, "kvstore", "", "Key-value store type")
	flags.Var(option.NewNamedMapOptions("kvstore-opts", &kvStoreOpts, nil), "kvstore-opt", "Key-value store options")
	flags.Uint16Var(&apiServerPort, "api-server-port", 9234, "Port on which the operator should serve API requests")

	flags.BoolVar(&synchronizeServices, "synchronize-k8s-services", true, "Synchronize Kubernetes services to kvstore")
	flags.BoolVar(&enableCepGC, "cilium-endpoint-gc", true, "Enable CiliumEndpoint garbage collector")
	flags.DurationVar(&identityGCInterval, "identity-gc-interval", time.Minute*10, "GC interval for security identities")
	flags.DurationVar(&kvNodeGCInterval, "nodes-gc-interval", time.Minute*2, "GC interval for nodes store in the kvstore")

	flags.IntVar(&unmanagedKubeDnsWatcherInterval, "unmanaged-pod-watcher-interval", 15, "Interval to check for unmanaged kube-dns pods (0 to disable)")

	// We need to obtain from Cilium ConfigMap if the CiliumEndpointCRD option
	// is enabled or disabled. This option is marked as hidden because the
	// Cilium Endpoint CRD controller is not in this program and by having it
	// being printed by operator --help could confuse users.
	flags.Bool(option.DisableCiliumEndpointCRDName, false, "")
	flags.MarkHidden(option.DisableCiliumEndpointCRDName)
	option.BindEnv(option.DisableCiliumEndpointCRDName)

	viper.BindPFlags(flags)

	// Make sure that klog logging variables are initialized so that we can
	// update them from this file.
	klog.InitFlags(nil)

	// Make sure klog (used by the client-go dependency) logs to stderr, as it
	// will try to log to directories that may not exist in the cilium-operator
	// container (/tmp) and cause the cilium-operator to exit.
	flag.Set("logtostderr", "true")
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if viper.GetBool("version") {
		fmt.Printf("Cilium %s\n", version.Version)
		os.Exit(0)
	}

	option.Config.ClusterName = viper.GetString(option.ClusterName)
	option.Config.ClusterID = viper.GetInt(option.ClusterIDName)
	option.Config.DisableCiliumEndpointCRD = viper.GetBool(option.DisableCiliumEndpointCRDName)

	viper.SetEnvPrefix("cilium")
	viper.SetConfigName("cilium-operator")
}

func runOperator(cmd *cobra.Command) {
	logging.SetupLogging([]string{}, map[string]string{}, "cilium-operator", viper.GetBool("debug"))

	log.Infof("Cilium Operator %s", version.Version)
	go StartServer(fmt.Sprintf(":%d", apiServerPort), shutdownSignal)

	if err := kvstore.Setup(kvStore, kvStoreOpts, nil); err != nil {
		log.WithError(err).WithFields(logrus.Fields{
			"kvstore": kvStore,
			"address": kvStoreOpts[fmt.Sprintf("%s.address", kvStore)],
		}).Fatal("Unable to setup kvstore")
	}

	k8s.Configure(k8sAPIServer, k8sKubeConfigPath)
	if err := k8s.Init(); err != nil {
		log.WithError(err).Fatal("Unable to connect to Kubernetes apiserver")
	}

	ciliumK8sClient = k8s.CiliumClient()
	k8sversion.Update(k8s.Client())
	if !k8sversion.Capabilities().MinimalVersionMet {
		log.Fatalf("Minimal kubernetes version not met: %s < %s",
			k8sversion.Version(), k8sversion.MinimalVersionConstraint)
	}

	if synchronizeServices {
		startSynchronizingServices()
	}

	if enableCepGC {
		enableCiliumEndpointSyncGC()
	}

	if err := runNodeWatcher(); err != nil {
		log.WithError(err).Error("Unable to setup node watcher")
	}

	if identityGCInterval != time.Duration(0) {
		startIdentityGC()
	}

	if option.Config.DisableCiliumEndpointCRD {
		log.Infof("KubeDNS unmanaged pods controller disabled as %q option is set to 'disabled' in Cilium ConfigMap", option.DisableCiliumEndpointCRDName)
	} else {
		enableUnmanagedKubeDNSController()
	}

	err := enableCNPWatcher()
	if err != nil {
		log.WithError(err).WithField("subsys", "CNPWatcher").Fatal(
			"Cannot connect to Kubernetes apiserver ")
	}

	<-shutdownSignal
	// graceful exit
	log.Info("Received termination signal. Shutting down")
	return
}
<|endoftext|>


<commit_before>package goka

import (
	"context"
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lovoo/goka/kafka"
	"github.com/lovoo/goka/logger"
	"github.com/lovoo/goka/multierr"
	"github.com/lovoo/goka/storage"

	"github.com/Shopify/sarama"
)

const (
	defaultPartitionChannelSize = 10
	stallPeriod                 = 30 * time.Second
	stalledTimeout              = 2 * time.Minute
)

// partition represents one partition of a group table and handles the updates to
// this table via UpdateCallback and ProcessCallback.
//
// partition can be started in two modes:
// - catchup-mode: used by views, starts with startCatchup(), only UpdateCallback called
// - processing-mode: used by processors, starts with start(),
//                    recovers table with UpdateCallback
//                    processes input streams with ProcessCallback
//
// The partition should never be called with a closed storage proxy.
// - Before starting the partition in either way, the client must open the storage proxy.
// - A partition may be restarted even if it returned errors. Before restarting
//   it, the client must call reinit().
// - To release all resources, after stopping the partition, the client must
//   close the storage proxy.
//
type partition struct {
	log   logger.Logger
	topic string

	ch      chan kafka.Event
	st      *storageProxy
	proxy   kafkaProxy
	process processCallback

	recoveredFlag int32
	hwm           int64
	offset        int64

	recoveredOnce sync.Once

	stats         *PartitionStats
	lastStats     *PartitionStats
	requestStats  chan bool
	responseStats chan *PartitionStats
}

type kafkaProxy interface {
	Add(string, int64) error
	Remove(string) error
	AddGroup()
	Stop()
}

type processCallback func(msg *message, st storage.Storage, wg *sync.WaitGroup, pstats *PartitionStats) (int, error)

func newPartition(log logger.Logger, topic string, cb processCallback, st *storageProxy, proxy kafkaProxy, channelSize int) *partition {
	return &partition{
		log:   log,
		topic: topic,

		ch:      make(chan kafka.Event, channelSize),
		st:      st,
		proxy:   proxy,
		process: cb,

		stats:         newPartitionStats(),
		lastStats:     newPartitionStats(),
		requestStats:  make(chan bool),
		responseStats: make(chan *PartitionStats, 1),
	}
}

// reinit reinitialzes the partition to connect to Kafka and start its goroutine
func (p *partition) reinit(proxy kafkaProxy) {
	if proxy != nil {
		p.proxy = proxy
	}
}

// start loads the table partition up to HWM and then consumes streams
func (p *partition) start(ctx context.Context) error {
	defer p.proxy.Stop()
	p.stats.Table.StartTime = time.Now()

	if p.st.Stateless() {
		if err := p.markRecovered(false); err != nil {
			return fmt.Errorf("error marking stateless partition as recovered: %v", err)
		}
	} else if err := p.recover(ctx); err != nil {
		return err
	}

	// if stopped, just return
	select {
	case <-ctx.Done():
		return nil
	default:
	}

	return p.run(ctx)
}

// startCatchup continually loads the table partition
func (p *partition) startCatchup(ctx context.Context) error {
	defer p.proxy.Stop()
	p.stats.Table.StartTime = time.Now()

	return p.catchup(ctx)
}

///////////////////////////////////////////////////////////////////////////////
// processing
///////////////////////////////////////////////////////////////////////////////
func newMessage(ev *kafka.Message) *message {
	return &message{
		Topic:     ev.Topic,
		Partition: ev.Partition,
		Offset:    ev.Offset,
		Timestamp: ev.Timestamp,
		Data:      ev.Value,
		Key:       ev.Key,
	}
}

func (p *partition) run(ctx context.Context) error {
	var wg sync.WaitGroup
	p.proxy.AddGroup()

	defer func() {
		done := make(chan struct{})
		go func() {
			wg.Wait()
			close(done)
		}()

		select {
		case <-done:
		case <-time.NewTimer(10 * time.Second).C:
			log.Printf("partition shutdown timed out. Will stop waiting.")
		}
	}()

	for {
		select {
		case ev, isOpen := <-p.ch:
			// channel already closed, ev will be nil
			if !isOpen {
				return nil
			}
			switch ev := ev.(type) {
			case *kafka.Message:
				if ev.Topic == p.topic {
					return fmt.Errorf("received message from group table topic after recovery: %s", p.topic)
				}

				updates, err := p.process(newMessage(ev), p.st, &wg, p.stats)
				if err != nil {
					return fmt.Errorf("error processing message: %v", err)
				}
				p.offset += int64(updates)
				p.hwm = p.offset + 1

				// metrics
				s := p.stats.Input[ev.Topic]
				s.Count++
				s.Bytes += len(ev.Value)
				if !ev.Timestamp.IsZero() {
					s.Delay = time.Since(ev.Timestamp)
				}
				p.stats.Input[ev.Topic] = s

			case *kafka.NOP:
				// don't do anything but also don't log.
			case *kafka.EOF:
			//	if ev.Topic != p.topic {
			//		return fmt.Errorf("received EOF of topic that is not ours. This should not happend (ours=%s, received=%s)", p.topic, ev.Topic)
			//	}
			default:
				return fmt.Errorf("load: cannot handle %T = %v", ev, ev)
			}

		case <-p.requestStats:
			p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
			select {
			case p.responseStats <- p.lastStats:
			case <-ctx.Done():
				p.log.Printf("Partitioning exiting, context is cancelled")
				return nil
			}

		case <-ctx.Done():
			p.log.Printf("Partitioning exiting, context is cancelled (outer)")
			return nil
		}

	}
}

///////////////////////////////////////////////////////////////////////////////
// loading storage
///////////////////////////////////////////////////////////////////////////////

func (p *partition) catchup(ctx context.Context) error {
	return p.load(ctx, true)
}

func (p *partition) recover(ctx context.Context) error {
	return p.load(ctx, false)
}

func (p *partition) recovered() bool {
	return atomic.LoadInt32(&p.recoveredFlag) == 1
}

func (p *partition) load(ctx context.Context, catchup bool) (rerr error) {
	// fetch local offset
	if local, err := p.st.GetOffset(sarama.OffsetOldest); err != nil {
		return fmt.Errorf("error reading local offset: %v", err)
	} else if err = p.proxy.Add(p.topic, local); err != nil {
		return err
	}

	defer func() {
		var derr multierr.Errors
		_ = derr.Collect(rerr)
		if e := p.proxy.Remove(p.topic); e != nil {
			_ = derr.Collect(e)
		}
		rerr = derr.NilOrError()
	}()

	stallTicker := time.NewTicker(stallPeriod)
	defer stallTicker.Stop()

	// reset stats after load
	defer p.stats.reset()

	var lastMessage time.Time
	for {
		select {
		case ev, isOpen := <-p.ch:

			// channel already closed, ev will be nil
			if !isOpen {
				return nil
			}

			switch ev := ev.(type) {
			case *kafka.BOF:
				p.hwm = ev.Hwm

				if ev.Offset == ev.Hwm {
					// nothing to recover
					if err := p.markRecovered(false); err != nil {
						return fmt.Errorf("error setting recovered: %v", err)
					}
				}

			case *kafka.EOF:
				p.offset = ev.Hwm - 1
				p.hwm = ev.Hwm

				if err := p.markRecovered(catchup); err != nil {
					return fmt.Errorf("error setting recovered: %v", err)
				}

				if catchup {
					continue
				}
				return nil

			case *kafka.Message:
				lastMessage = time.Now()
				if ev.Topic != p.topic {
					p.log.Printf("dropping message from topic = %s while loading", ev.Topic)
					continue
				}
				if err := p.storeEvent(ev); err != nil {
					return fmt.Errorf("load: error updating storage: %v", err)
				}
				p.offset = ev.Offset
				if p.offset >= p.hwm-1 {
					if err := p.markRecovered(catchup); err != nil {
						return fmt.Errorf("error setting recovered: %v", err)
					}
				}

				// update metrics
				s := p.stats.Input[ev.Topic]
				s.Count++
				s.Bytes += len(ev.Value)
				if !ev.Timestamp.IsZero() {
					s.Delay = time.Since(ev.Timestamp)
				}
				p.stats.Input[ev.Topic] = s
				p.stats.Table.Stalled = false

			case *kafka.NOP:
				// don't do anything

			default:
				return fmt.Errorf("load: cannot handle %T = %v", ev, ev)
			}

		case now := <-stallTicker.C:
			// only set to stalled, if the last message was earlier
			// than the stalled timeout
			if now.Sub(lastMessage) > stalledTimeout {
				p.stats.Table.Stalled = true
			}

		case <-p.requestStats:
			p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
			select {
			case p.responseStats <- p.lastStats:
			case <-ctx.Done():
				return nil
			}

		case <-ctx.Done():
			return nil
		}
	}
}

func (p *partition) storeEvent(msg *kafka.Message) error {
	err := p.st.Update(msg.Key, msg.Value)
	if err != nil {
		return fmt.Errorf("Error from the update callback while recovering from the log: %v", err)
	}
	err = p.st.SetOffset(msg.Offset)
	if err != nil {
		return fmt.Errorf("Error updating offset in local storage while recovering from the log: %v", err)
	}
	return nil
}

// mark storage as recovered
func (p *partition) markRecovered(catchup bool) (err error) {
	p.recoveredOnce.Do(func() {
		p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
		p.lastStats.Table.Status = PartitionPreparing

		var (
			done = make(chan bool)
			wg   sync.WaitGroup
		)
		if catchup {
			// if catching up (views), stop reading from topic before marking
			// partition as recovered to avoid blocking other partitions when
			// p.ch gets full
			if err = p.proxy.Remove(p.topic); err != nil {
				return
			}

			// drain events channel -- we'll fetch them again later
			wg.Add(1)
			go func() {
				defer wg.Done()
				for {
					select {
					case <-p.ch:
					case <-done:
						return
					}
				}
			}()
		}

		// mark storage as recovered -- this may take long
		if err = p.st.MarkRecovered(); err != nil {
			close(done)
			return
		}

		if catchup {
			close(done)
			wg.Wait()
			// start reading from topic again if in catchup mode
			if err = p.proxy.Add(p.topic, p.hwm); err != nil {
				return
			}
		}

		// update stats
		p.stats.Table.Status = PartitionRunning
		p.stats.Table.RecoveryTime = time.Now()

		atomic.StoreInt32(&p.recoveredFlag, 1)
	})

	// Be sure to mark partition as not stalled after EOF arrives, as
	// this will not be written in the run-method
	p.stats.Table.Stalled = false
	return
}

func (p *partition) fetchStats(ctx context.Context) *PartitionStats {
	timer := time.NewTimer(100 * time.Millisecond)
	defer timer.Stop()

	select {
	case p.requestStats <- true:
	case <-ctx.Done():
		return newPartitionStats().init(p.lastStats, p.offset, p.hwm)
	case <-timer.C:
		return p.lastStats
	}

	select {
	case s := <-p.responseStats:
		return s
	case <-ctx.Done():
		return newPartitionStats().init(p.lastStats, p.offset, p.hwm)
	}
}
<commit_msg>repeating messages from input stream after recover<commit_after>package goka

import (
	"context"
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lovoo/goka/kafka"
	"github.com/lovoo/goka/logger"
	"github.com/lovoo/goka/multierr"
	"github.com/lovoo/goka/storage"

	"github.com/Shopify/sarama"
)

const (
	defaultPartitionChannelSize = 10
	stallPeriod                 = 30 * time.Second
	stalledTimeout              = 2 * time.Minute
)

// partition represents one partition of a group table and handles the updates to
// this table via UpdateCallback and ProcessCallback.
//
// partition can be started in two modes:
// - catchup-mode: used by views, starts with startCatchup(), only UpdateCallback called
// - processing-mode: used by processors, starts with start(),
//                    recovers table with UpdateCallback
//                    processes input streams with ProcessCallback
//
// The partition should never be called with a closed storage proxy.
// - Before starting the partition in either way, the client must open the storage proxy.
// - A partition may be restarted even if it returned errors. Before restarting
//   it, the client must call reinit().
// - To release all resources, after stopping the partition, the client must
//   close the storage proxy.
//
type partition struct {
	log   logger.Logger
	topic string

	ch      chan kafka.Event
	st      *storageProxy
	proxy   kafkaProxy
	process processCallback

	recoveredFlag int32
	hwm           int64
	offset        int64

	recoveredOnce sync.Once

	stats         *PartitionStats
	lastStats     *PartitionStats
	requestStats  chan bool
	responseStats chan *PartitionStats

	droppedEvents []kafka.Event
}

type kafkaProxy interface {
	Add(string, int64) error
	Remove(string) error
	AddGroup()
	Stop()
}

type processCallback func(msg *message, st storage.Storage, wg *sync.WaitGroup, pstats *PartitionStats) (int, error)

func newPartition(log logger.Logger, topic string, cb processCallback, st *storageProxy, proxy kafkaProxy, channelSize int) *partition {
	return &partition{
		log:   log,
		topic: topic,

		ch:      make(chan kafka.Event, channelSize),
		st:      st,
		proxy:   proxy,
		process: cb,

		stats:         newPartitionStats(),
		lastStats:     newPartitionStats(),
		requestStats:  make(chan bool),
		responseStats: make(chan *PartitionStats, 1),
	}
}

// reinit reinitialzes the partition to connect to Kafka and start its goroutine
func (p *partition) reinit(proxy kafkaProxy) {
	if proxy != nil {
		p.proxy = proxy
	}
}

// start loads the table partition up to HWM and then consumes streams
func (p *partition) start(ctx context.Context) error {
	defer p.proxy.Stop()
	p.stats.Table.StartTime = time.Now()

	// init events
	p.droppedEvents = make([]kafka.Event, 0)

	if p.st.Stateless() {
		if err := p.markRecovered(false); err != nil {
			return fmt.Errorf("error marking stateless partition as recovered: %v", err)
		}
	} else if err := p.recover(ctx); err != nil {
		return err
	}

	// if stopped, just return
	select {
	case <-ctx.Done():
		return nil
	default:
	}

	return p.run(ctx)
}

// startCatchup continually loads the table partition
func (p *partition) startCatchup(ctx context.Context) error {
	defer p.proxy.Stop()
	p.stats.Table.StartTime = time.Now()

	return p.catchup(ctx)
}

///////////////////////////////////////////////////////////////////////////////
// processing
///////////////////////////////////////////////////////////////////////////////
func newMessage(ev *kafka.Message) *message {
	return &message{
		Topic:     ev.Topic,
		Partition: ev.Partition,
		Offset:    ev.Offset,
		Timestamp: ev.Timestamp,
		Data:      ev.Value,
		Key:       ev.Key,
	}
}

func (p *partition) run(ctx context.Context) error {
	var wg sync.WaitGroup
	p.proxy.AddGroup()

	defer func() {
		done := make(chan struct{})
		go func() {
			wg.Wait()
			close(done)
		}()

		select {
		case <-done:
		case <-time.NewTimer(10 * time.Second).C:
			log.Printf("partition shutdown timed out. Will stop waiting.")
		}
	}()

	// recover the dropped events
	for _, ev := range p.droppedEvents {
		select {
		case p.ch <- ev:
		case <-ctx.Done():
			return nil
		}
	}

	for {
		select {
		case ev, isOpen := <-p.ch:
			// channel already closed, ev will be nil
			if !isOpen {
				return nil
			}
			switch ev := ev.(type) {
			case *kafka.Message:
				if ev.Topic == p.topic {
					return fmt.Errorf("received message from group table topic after recovery: %s", p.topic)
				}

				updates, err := p.process(newMessage(ev), p.st, &wg, p.stats)
				if err != nil {
					return fmt.Errorf("error processing message: %v", err)
				}
				p.offset += int64(updates)
				p.hwm = p.offset + 1

				// metrics
				s := p.stats.Input[ev.Topic]
				s.Count++
				s.Bytes += len(ev.Value)
				if !ev.Timestamp.IsZero() {
					s.Delay = time.Since(ev.Timestamp)
				}
				p.stats.Input[ev.Topic] = s

			case *kafka.NOP:
				// don't do anything but also don't log.
			case *kafka.EOF:
			//	if ev.Topic != p.topic {
			//		return fmt.Errorf("received EOF of topic that is not ours. This should not happend (ours=%s, received=%s)", p.topic, ev.Topic)
			//	}
			default:
				return fmt.Errorf("load: cannot handle %T = %v", ev, ev)
			}

		case <-p.requestStats:
			p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
			select {
			case p.responseStats <- p.lastStats:
			case <-ctx.Done():
				p.log.Printf("Partitioning exiting, context is cancelled")
				return nil
			}

		case <-ctx.Done():
			p.log.Printf("Partitioning exiting, context is cancelled (outer)")
			return nil
		}

	}
}

///////////////////////////////////////////////////////////////////////////////
// loading storage
///////////////////////////////////////////////////////////////////////////////

func (p *partition) catchup(ctx context.Context) error {
	return p.load(ctx, true)
}

func (p *partition) recover(ctx context.Context) error {
	return p.load(ctx, false)
}

func (p *partition) recovered() bool {
	return atomic.LoadInt32(&p.recoveredFlag) == 1
}

func (p *partition) load(ctx context.Context, catchup bool) (rerr error) {
	// fetch local offset
	if local, err := p.st.GetOffset(sarama.OffsetOldest); err != nil {
		return fmt.Errorf("error reading local offset: %v", err)
	} else if err = p.proxy.Add(p.topic, local); err != nil {
		return err
	}

	defer func() {
		var derr multierr.Errors
		_ = derr.Collect(rerr)
		if e := p.proxy.Remove(p.topic); e != nil {
			_ = derr.Collect(e)
		}
		rerr = derr.NilOrError()
	}()

	stallTicker := time.NewTicker(stallPeriod)
	defer stallTicker.Stop()

	// reset stats after load
	defer p.stats.reset()

	var lastMessage time.Time
	for {
		select {
		case ev, isOpen := <-p.ch:

			// channel already closed, ev will be nil
			if !isOpen {
				return nil
			}

			switch ev := ev.(type) {
			case *kafka.BOF:
				p.hwm = ev.Hwm

				if ev.Offset == ev.Hwm {
					// nothing to recover
					if err := p.markRecovered(false); err != nil {
						return fmt.Errorf("error setting recovered: %v", err)
					}
				}

			case *kafka.EOF:
				p.offset = ev.Hwm - 1
				p.hwm = ev.Hwm

				if err := p.markRecovered(catchup); err != nil {
					return fmt.Errorf("error setting recovered: %v", err)
				}

				if catchup {
					continue
				}
				return nil

			case *kafka.Message:
				lastMessage = time.Now()
				if ev.Topic != p.topic {
					p.log.Printf("dropping message from topic = %s while loading", ev.Topic)
					// saving the dropped messages from input stream
					p.droppedEvents = append(p.droppedEvents, ev)
					continue
				}
				if err := p.storeEvent(ev); err != nil {
					return fmt.Errorf("load: error updating storage: %v", err)
				}
				p.offset = ev.Offset
				if p.offset >= p.hwm-1 {
					if err := p.markRecovered(catchup); err != nil {
						return fmt.Errorf("error setting recovered: %v", err)
					}
				}

				// update metrics
				s := p.stats.Input[ev.Topic]
				s.Count++
				s.Bytes += len(ev.Value)
				if !ev.Timestamp.IsZero() {
					s.Delay = time.Since(ev.Timestamp)
				}
				p.stats.Input[ev.Topic] = s
				p.stats.Table.Stalled = false

			case *kafka.NOP:
				// don't do anything

			default:
				return fmt.Errorf("load: cannot handle %T = %v", ev, ev)
			}

		case now := <-stallTicker.C:
			// only set to stalled, if the last message was earlier
			// than the stalled timeout
			if now.Sub(lastMessage) > stalledTimeout {
				p.stats.Table.Stalled = true
			}

		case <-p.requestStats:
			p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
			select {
			case p.responseStats <- p.lastStats:
			case <-ctx.Done():
				return nil
			}

		case <-ctx.Done():
			return nil
		}
	}
}

func (p *partition) storeEvent(msg *kafka.Message) error {
	err := p.st.Update(msg.Key, msg.Value)
	if err != nil {
		return fmt.Errorf("Error from the update callback while recovering from the log: %v", err)
	}
	err = p.st.SetOffset(msg.Offset)
	if err != nil {
		return fmt.Errorf("Error updating offset in local storage while recovering from the log: %v", err)
	}
	return nil
}

// mark storage as recovered
func (p *partition) markRecovered(catchup bool) (err error) {
	p.recoveredOnce.Do(func() {
		p.lastStats = newPartitionStats().init(p.stats, p.offset, p.hwm)
		p.lastStats.Table.Status = PartitionPreparing

		var (
			done = make(chan bool)
			wg   sync.WaitGroup
		)
		if catchup {
			// if catching up (views), stop reading from topic before marking
			// partition as recovered to avoid blocking other partitions when
			// p.ch gets full
			if err = p.proxy.Remove(p.topic); err != nil {
				return
			}

			// drain events channel -- we'll fetch them again later
			wg.Add(1)
			go func() {
				defer wg.Done()
				for {
					select {
					case <-p.ch:
					case <-done:
						return
					}
				}
			}()
		}

		// mark storage as recovered -- this may take long
		if err = p.st.MarkRecovered(); err != nil {
			close(done)
			return
		}

		if catchup {
			close(done)
			wg.Wait()
			// start reading from topic again if in catchup mode
			if err = p.proxy.Add(p.topic, p.hwm); err != nil {
				return
			}
		}

		// update stats
		p.stats.Table.Status = PartitionRunning
		p.stats.Table.RecoveryTime = time.Now()

		atomic.StoreInt32(&p.recoveredFlag, 1)
	})

	// Be sure to mark partition as not stalled after EOF arrives, as
	// this will not be written in the run-method
	p.stats.Table.Stalled = false
	return
}

func (p *partition) fetchStats(ctx context.Context) *PartitionStats {
	timer := time.NewTimer(100 * time.Millisecond)
	defer timer.Stop()

	select {
	case p.requestStats <- true:
	case <-ctx.Done():
		return newPartitionStats().init(p.lastStats, p.offset, p.hwm)
	case <-timer.C:
		return p.lastStats
	}

	select {
	case s := <-p.responseStats:
		return s
	case <-ctx.Done():
		return newPartitionStats().init(p.lastStats, p.offset, p.hwm)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Intel Corporation
//
// 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.

package service

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/01org/ciao/openstack/identity"
	"github.com/01org/ciao/openstack/image"
	"github.com/01org/ciao/ssntp/uuid"
	"github.com/gorilla/mux"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
)

// State represents the state of the image.
type State string

const (
	// Created means that an empty image has been created
	Created State = "created"
)

func stateToStatus(state State) image.Status {
	switch state {
	case Created:
		return image.Queued
	}

	return image.Active
}

func visibility(i Image) image.Visibility {
	if i.TenantID == "" {
		return image.Public
	}
	return image.Private
}

// Type represents the valid image types.
type Type string

const (
	// Raw is the raw image format.
	Raw Type = "raw"

	// QCow is the qcow2 format.
	QCow Type = "qcow2"

	// ISO is the iso format.
	ISO Type = "iso"
)

// Image contains the information that ciao will store about the image
type Image struct {
	ID         string
	State      State
	TenantID   string
	Name       string
	CreateTime time.Time
	Type       Type
}

// Datastore is the datastore interface that the service implementation
// requires.
type Datastore interface {
	Create(Image) error
	RetrieveAll() ([]Image, error)
	Retrieve(ID string) (Image, error)
	Update(Image) error
	Delete(ID string) error
}

var (
	// ErrNoImage is returned when an image is not found.
	ErrNoImage = errors.New("Image not found")
)

// ImageService is the context for the image service implementation.
type ImageService struct {
	ds Datastore
}

// CreateImage will create an empty image in the image datastore.
func (is ImageService) CreateImage(req image.CreateImageRequest) (image.CreateImageResponse, error) {
	// create an ImageInfo struct and store it in our image
	// datastore.
	i := Image{
		ID:         uuid.Generate().String(),
		State:      Created,
		Name:       req.Name,
		CreateTime: time.Now(),
	}

	err := is.ds.Create(i)
	if err != nil {
		return image.CreateImageResponse{}, err
	}

	return image.CreateImageResponse{
		Status:     image.Queued,
		CreatedAt:  i.CreateTime,
		Tags:       make([]string, 0),
		Locations:  make([]string, 0),
		DiskFormat: image.Raw,
		Visibility: visibility(i),
		Self:       fmt.Sprintf("/v2/images/%s", i.ID),
		Protected:  false,
		ID:         i.ID,
		File:       fmt.Sprintf("/v2/images/%s/file", i.ID),
		Schema:     "/v2/schemas/image",
		Name:       &i.Name,
	}, nil
}

// ListImages will return a list of all the images in the datastore.
func (is ImageService) ListImages() ([]image.CreateImageResponse, error) {
	var response []image.CreateImageResponse

	images, err := is.ds.RetrieveAll()
	if err != nil {
		return response, err
	}

	for _, img := range images {
		i := image.CreateImageResponse{
			Status:     stateToStatus(img.State),
			CreatedAt:  img.CreateTime,
			Tags:       make([]string, 0),
			Locations:  make([]string, 0),
			DiskFormat: image.DiskFormat(img.Type),
			Visibility: visibility(img),
			Self:       fmt.Sprintf("/v2/images/%s", img.ID),
			Protected:  false,
			ID:         img.ID,
			File:       fmt.Sprintf("/v2/images/%s/file", img.ID),
			Schema:     "/v2/schemas/image",
			Name:       &img.Name,
		}

		response = append(response, i)
	}

	return response, nil
}

// Config is required to setup the API context for the image service.
type Config struct {
	// Port represents the http port that should be used for the service.
	Port int

	// HTTPSCACert is the path to the http ca cert to use.
	HTTPSCACert string

	// HTTPSKey is the path to the https cert key.
	HTTPSKey string

	// Datastore is an interface to a persistent datastore.
	Datastore Datastore

	// IdentityEndpoint is the location of the keystone service.
	IdentityEndpoint string

	// Username is the service username for the image service in keystone.
	Username string

	// Password is the password for the image service user in keystone.
	Password string
}

func getIdentityClient(config Config) (*gophercloud.ServiceClient, error) {
	opt := gophercloud.AuthOptions{
		IdentityEndpoint: config.IdentityEndpoint + "v3/",
		Username:         config.Username,
		Password:         config.Password,
		TenantName:       "service",
		DomainID:         "default",
		AllowReauth:      true,
	}
	provider, err := openstack.AuthenticatedClient(opt)
	if err != nil {
		return nil, err
	}

	v3client := openstack.NewIdentityV3(provider)
	if v3client == nil {
		return nil, errors.New("Unable to get keystone V3 client")
	}

	return v3client, nil
}

// Start will get the Image API endpoints from the OpenStack image api,
// then wrap them in keystone validation. It will then start the https
// service.
func Start(config Config) error {
	is := ImageService{config.Datastore}

	apiConfig := image.APIConfig{
		Port:         config.Port,
		ImageService: is,
	}

	// get our routes.
	r := image.Routes(apiConfig)

	// setup identity for these routes.
	validServices := []identity.ValidService{
		{"image", "ciao"},
		{"image", "glance"},
	}

	validAdmins := []identity.ValidAdmin{
		{"service", "admin"},
		{"admin", "admin"},
	}

	client, err := getIdentityClient(config)
	if err != nil {
		return err
	}

	err = r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
		h := identity.Handler{
			Client:        client,
			Next:          route.GetHandler(),
			ValidServices: validServices,
			ValidAdmins:   validAdmins,
		}

		route.Handler(h)

		return nil
	})

	// start service.
	service := fmt.Sprintf(":%d", config.Port)

	return http.ListenAndServeTLS(service, config.HTTPSCACert, config.HTTPSKey, r)
}
<commit_msg>ciao-image: service: return error if gorilla mux walk fails<commit_after>// Copyright (c) 2016 Intel Corporation
//
// 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.

package service

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/01org/ciao/openstack/identity"
	"github.com/01org/ciao/openstack/image"
	"github.com/01org/ciao/ssntp/uuid"
	"github.com/gorilla/mux"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
)

// State represents the state of the image.
type State string

const (
	// Created means that an empty image has been created
	Created State = "created"
)

func stateToStatus(state State) image.Status {
	switch state {
	case Created:
		return image.Queued
	}

	return image.Active
}

func visibility(i Image) image.Visibility {
	if i.TenantID == "" {
		return image.Public
	}
	return image.Private
}

// Type represents the valid image types.
type Type string

const (
	// Raw is the raw image format.
	Raw Type = "raw"

	// QCow is the qcow2 format.
	QCow Type = "qcow2"

	// ISO is the iso format.
	ISO Type = "iso"
)

// Image contains the information that ciao will store about the image
type Image struct {
	ID         string
	State      State
	TenantID   string
	Name       string
	CreateTime time.Time
	Type       Type
}

// Datastore is the datastore interface that the service implementation
// requires.
type Datastore interface {
	Create(Image) error
	RetrieveAll() ([]Image, error)
	Retrieve(ID string) (Image, error)
	Update(Image) error
	Delete(ID string) error
}

var (
	// ErrNoImage is returned when an image is not found.
	ErrNoImage = errors.New("Image not found")
)

// ImageService is the context for the image service implementation.
type ImageService struct {
	ds Datastore
}

// CreateImage will create an empty image in the image datastore.
func (is ImageService) CreateImage(req image.CreateImageRequest) (image.CreateImageResponse, error) {
	// create an ImageInfo struct and store it in our image
	// datastore.
	i := Image{
		ID:         uuid.Generate().String(),
		State:      Created,
		Name:       req.Name,
		CreateTime: time.Now(),
	}

	err := is.ds.Create(i)
	if err != nil {
		return image.CreateImageResponse{}, err
	}

	return image.CreateImageResponse{
		Status:     image.Queued,
		CreatedAt:  i.CreateTime,
		Tags:       make([]string, 0),
		Locations:  make([]string, 0),
		DiskFormat: image.Raw,
		Visibility: visibility(i),
		Self:       fmt.Sprintf("/v2/images/%s", i.ID),
		Protected:  false,
		ID:         i.ID,
		File:       fmt.Sprintf("/v2/images/%s/file", i.ID),
		Schema:     "/v2/schemas/image",
		Name:       &i.Name,
	}, nil
}

// ListImages will return a list of all the images in the datastore.
func (is ImageService) ListImages() ([]image.CreateImageResponse, error) {
	var response []image.CreateImageResponse

	images, err := is.ds.RetrieveAll()
	if err != nil {
		return response, err
	}

	for _, img := range images {
		i := image.CreateImageResponse{
			Status:     stateToStatus(img.State),
			CreatedAt:  img.CreateTime,
			Tags:       make([]string, 0),
			Locations:  make([]string, 0),
			DiskFormat: image.DiskFormat(img.Type),
			Visibility: visibility(img),
			Self:       fmt.Sprintf("/v2/images/%s", img.ID),
			Protected:  false,
			ID:         img.ID,
			File:       fmt.Sprintf("/v2/images/%s/file", img.ID),
			Schema:     "/v2/schemas/image",
			Name:       &img.Name,
		}

		response = append(response, i)
	}

	return response, nil
}

// Config is required to setup the API context for the image service.
type Config struct {
	// Port represents the http port that should be used for the service.
	Port int

	// HTTPSCACert is the path to the http ca cert to use.
	HTTPSCACert string

	// HTTPSKey is the path to the https cert key.
	HTTPSKey string

	// Datastore is an interface to a persistent datastore.
	Datastore Datastore

	// IdentityEndpoint is the location of the keystone service.
	IdentityEndpoint string

	// Username is the service username for the image service in keystone.
	Username string

	// Password is the password for the image service user in keystone.
	Password string
}

func getIdentityClient(config Config) (*gophercloud.ServiceClient, error) {
	opt := gophercloud.AuthOptions{
		IdentityEndpoint: config.IdentityEndpoint + "v3/",
		Username:         config.Username,
		Password:         config.Password,
		TenantName:       "service",
		DomainID:         "default",
		AllowReauth:      true,
	}
	provider, err := openstack.AuthenticatedClient(opt)
	if err != nil {
		return nil, err
	}

	v3client := openstack.NewIdentityV3(provider)
	if v3client == nil {
		return nil, errors.New("Unable to get keystone V3 client")
	}

	return v3client, nil
}

// Start will get the Image API endpoints from the OpenStack image api,
// then wrap them in keystone validation. It will then start the https
// service.
func Start(config Config) error {
	is := ImageService{config.Datastore}

	apiConfig := image.APIConfig{
		Port:         config.Port,
		ImageService: is,
	}

	// get our routes.
	r := image.Routes(apiConfig)

	// setup identity for these routes.
	validServices := []identity.ValidService{
		{"image", "ciao"},
		{"image", "glance"},
	}

	validAdmins := []identity.ValidAdmin{
		{"service", "admin"},
		{"admin", "admin"},
	}

	client, err := getIdentityClient(config)
	if err != nil {
		return err
	}

	err = r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
		h := identity.Handler{
			Client:        client,
			Next:          route.GetHandler(),
			ValidServices: validServices,
			ValidAdmins:   validAdmins,
		}

		route.Handler(h)

		return nil
	})
	if err != nil {
		return err
	}

	// start service.
	service := fmt.Sprintf(":%d", config.Port)

	return http.ListenAndServeTLS(service, config.HTTPSCACert, config.HTTPSKey, r)
}
<|endoftext|>


<commit_before>package client

import (
	"net/http"
)

func init() {
	Register("http_testing", NewHTTPClient, "benchmark for http server")
}

type httpClient struct {
	cli *http.Client
	url string
}

func NewHTTPClient(flag *FlagSet) Client {
	c := new(httpClient)
	flag.StringVar(&c.url, "url", "", "set the url you request")
	flag.Parse()
	return c
}

func (c *httpClient) Dial(addr string) error {
	c.cli = new(http.Client)
	return nil
}

func (c *httpClient) Request() error {
	c.cli.Get(c.url)
	return nil
}
<commit_msg>Handles error of http_testing request<commit_after>package client

import (
	"net/http"
)

func init() {
	Register("http_testing", NewHTTPClient, "benchmark for http server")
}

type httpClient struct {
	cli *http.Client
	url string
}

func NewHTTPClient(flag *FlagSet) Client {
	c := new(httpClient)
	flag.StringVar(&c.url, "url", "", "set the url you request")
	flag.Parse()
	return c
}

func (c *httpClient) Dial(addr string) error {
	c.cli = new(http.Client)
	return nil
}

func (c *httpClient) Request() error {
	_, err := c.cli.Get(c.url)
	return err
}
<|endoftext|>


<commit_before>package web

import (
	"github.com/zenoss/serviced/domain"

	"fmt"
)

//profile defines meta-data for the host/pool resource's metrics and graphs
var (
	zero       int = 0
	onehundred int = 100

	profile = domain.MonitorProfile{
		MetricConfigs: []domain.MetricConfig{
			//CPU
			domain.MetricConfig{
				ID:          "cpu",
				Name:        "CPU Usage",
				Description: "CPU Statistics",
				Metrics: []domain.Metric{
					domain.Metric{ID: "cpu.system", Name: "CPU System"},
					domain.Metric{ID: "cpu.user", Name: "CPU User"},
					domain.Metric{ID: "cpu.idle", Name: "CPU Idle"},
					domain.Metric{ID: "cpu.iowait", Name: "CPU IO Wait"},
					domain.Metric{ID: "cpu.nice", Name: "CPU Nice"},
				},
			},
			//Memory
			domain.MetricConfig{
				ID:          "memory",
				Name:        "Memory Usage",
				Description: "Memory Usage Statistics -- /proc/meminfo",
				Metrics: []domain.Metric{
					domain.Metric{ID: "memory.buffers", Name: "Memory Buffer"},
					domain.Metric{ID: "memory.cached", Name: "Memory Cache"},
					domain.Metric{ID: "memory.free", Name: "Memory Free"},
					domain.Metric{ID: "memory.used", Name: "Used Memory"},
					domain.Metric{ID: "memory.actualfree", Name: "Actual Free Memory"},
					domain.Metric{ID: "memory.actualused", Name: "Actual Used Memory"},
					domain.Metric{ID: "swap.total", Name: "Total Swap"},
					domain.Metric{ID: "swap.free", Name: "Free Swap"},
				},
			},
			//Virtual Memory
			domain.MetricConfig{
				ID:          "virtual.memory",
				Name:        "Virtual Memory Usage",
				Description: "Virtual Memory Usage Statistics -- /proc/vmstat",
				Metrics: []domain.Metric{
					domain.Metric{ID: "vmstat.pgfault", Name: "Minor Page Fault"},
					domain.Metric{ID: "vmstat.pgmajfault", Name: "Major Page Fault"},
				},
			},
			//Files
			domain.MetricConfig{
				ID:          "files",
				Name:        "File Usage",
				Description: "File Statistics",
				Metrics: []domain.Metric{
					domain.Metric{ID: "Serviced.OpenFileDescriptors", Name: "OpenFileDescriptors"},
				},
			},
		},
	}
)

//Open File Descriptors
func newOpenFileDescriptorsGraph(tags map[string][]string) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				ID:           "ofd",
				Aggregator:   "avg",
				Color:        "#aec7e8",
				Fill:         false,
				Format:       "%6.2f",
				Legend:       "Serviced Open File Descriptors",
				Metric:       "Serviced.OpenFileDescriptors",
				MetricSource: "files",
				Name:         "Serviced Open File Descriptors",
				Rate:         false,
				Type:         "line",
			},
		},
		ID:     "serviced.ofd",
		Name:   "Serviced Open File Descriptors",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of serviced's total open file descriptors over time",
	}
}

//Major Page Faults
func newMajorPageFaultGraph(tags map[string][]string) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				ID:           "pgfault",
				Color:        "#aec7e8",
				Fill:         false,
				Format:       "%d",
				Legend:       "Major Page Faults",
				Metric:       "vmstat.pgmajfault",
				MetricSource: "virtual.memory",
				Name:         "Major Page Faults",
				Rate:         true,
				Type:         "line",
			},
		},
		ID:     "memory.major.pagefault",
		Name:   "Memory Major Page Faults",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "Faults / Min",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of major memory page faults over time",
	}
}

//Cpu Usage
func newCpuConfigGraph(tags map[string][]string, totalCores int) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				Color:        "#aec7e8",
				Expression:   fmt.Sprintf("rpn:%d,/,100,*,60,/", totalCores),
				Fill:         false,
				Format:       "%6.2f",
				ID:           "system",
				Legend:       "CPU (System)",
				Metric:       "cpu.system",
				MetricSource: "cpu",
				Name:         "CPU (System)",
				Rate:         true,
				Type:         "line",
			},
			domain.DataPoint{
				Aggregator:   "avg",
				Color:        "#98df8a",
				Expression:   fmt.Sprintf("rpn:%d,/,100,*,60,/", totalCores),
				ID:           "user",
				Fill:         false,
				Format:       "%6.2f",
				Legend:       "CPU (User)",
				Metric:       "cpu.user",
				MetricSource: "cpu",
				Name:         "CPU (User)",
				Rate:         true,
				Type:         "line",
			},
		},
		ID:     "cpu.usage",
		Name:   "CPU Usage",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		MaxY:   &onehundred,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "% Used",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of system and user cpu usage over time",
	}
}

func newRSSConfigGraph(tags map[string][]string, totalMemory uint64) domain.GraphConfig {
	MaxY := int(totalMemory / 1024 / 1024 / 1024)
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				Expression:   "rpn:1024,/,1024,/,1024,/",
				Color:        "#aec7e8",
				Fill:         true,
				Format:       "%6.2f",
				Legend:       "Used (total - free)",
				Metric:       "memory.actualused",
				MetricSource: "memory",
				Name:         "Used",
				Type:         "area",
				ID:           "used",
			},
			domain.DataPoint{
				Aggregator:   "avg",
				Expression:   "rpn:1024,/,1024,/,1024,/",
				Color:        "#98df8a",
				Fill:         true,
				Format:       "%6.2f",
				Legend:       "Free (-buffers/+cached)",
				Metric:       "memory.actualfree",
				MetricSource: "memory",
				Name:         "Free",
				ID:           "Memory",
				Type:         "area",
			},
		},
		ID:     "memory.usage",
		Name:   "Memory Usage",
		Footer: false,
		Format: "%6.2f",
		MaxY:   &MaxY,
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "GB",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of memory free (-buffers/+cache) vs used (total - free) over time",
	}
}

//newProfile builds a MonitoringProfile without graphs
func newProfile(tags map[string][]string) (domain.MonitorProfile, error) {
	p := domain.MonitorProfile{
		MetricConfigs: make([]domain.MetricConfig, len(profile.MetricConfigs)),
	}

	build, err := domain.NewMetricConfigBuilder("/metrics/api/performance/query", "POST")
	if err != nil {
		return p, err
	}

	//add metrics to profile
	for i := range profile.MetricConfigs {
		metricConfig := &profile.MetricConfigs[i]
		for j := range metricConfig.Metrics {
			metric := &metricConfig.Metrics[j]
			build.Metric(metric.ID, metric.Name).SetTags(tags)
		}

		config, err := build.Config(metricConfig.ID, metricConfig.Name, metricConfig.Description, "1h-ago")
		if err != nil {
			return p, err
		}
		p.MetricConfigs[i] = *config
	}
	return p, nil
}
<commit_msg>remove used insteadof total in metrics metadata<commit_after>package web

import (
	"github.com/zenoss/serviced/domain"

	"fmt"
)

//profile defines meta-data for the host/pool resource's metrics and graphs
var (
	zero       int = 0
	onehundred int = 100

	profile = domain.MonitorProfile{
		MetricConfigs: []domain.MetricConfig{
			//CPU
			domain.MetricConfig{
				ID:          "cpu",
				Name:        "CPU Usage",
				Description: "CPU Statistics",
				Metrics: []domain.Metric{
					domain.Metric{ID: "cpu.system", Name: "CPU System"},
					domain.Metric{ID: "cpu.user", Name: "CPU User"},
					domain.Metric{ID: "cpu.idle", Name: "CPU Idle"},
					domain.Metric{ID: "cpu.iowait", Name: "CPU IO Wait"},
					domain.Metric{ID: "cpu.nice", Name: "CPU Nice"},
				},
			},
			//Memory
			domain.MetricConfig{
				ID:          "memory",
				Name:        "Memory Usage",
				Description: "Memory Usage Statistics -- /proc/meminfo",
				Metrics: []domain.Metric{
					domain.Metric{ID: "memory.buffers", Name: "Memory Buffer"},
					domain.Metric{ID: "memory.cached", Name: "Memory Cache"},
					domain.Metric{ID: "memory.free", Name: "Memory Free"},
					domain.Metric{ID: "memory.total", Name: "Total Memory"},
					domain.Metric{ID: "memory.actualfree", Name: "Actual Free Memory"},
					domain.Metric{ID: "memory.actualused", Name: "Actual Used Memory"},
					domain.Metric{ID: "swap.total", Name: "Total Swap"},
					domain.Metric{ID: "swap.free", Name: "Free Swap"},
				},
			},
			//Virtual Memory
			domain.MetricConfig{
				ID:          "virtual.memory",
				Name:        "Virtual Memory Usage",
				Description: "Virtual Memory Usage Statistics -- /proc/vmstat",
				Metrics: []domain.Metric{
					domain.Metric{ID: "vmstat.pgfault", Name: "Minor Page Fault"},
					domain.Metric{ID: "vmstat.pgmajfault", Name: "Major Page Fault"},
				},
			},
			//Files
			domain.MetricConfig{
				ID:          "files",
				Name:        "File Usage",
				Description: "File Statistics",
				Metrics: []domain.Metric{
					domain.Metric{ID: "Serviced.OpenFileDescriptors", Name: "OpenFileDescriptors"},
				},
			},
		},
	}
)

//Open File Descriptors
func newOpenFileDescriptorsGraph(tags map[string][]string) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				ID:           "ofd",
				Aggregator:   "avg",
				Color:        "#aec7e8",
				Fill:         false,
				Format:       "%6.2f",
				Legend:       "Serviced Open File Descriptors",
				Metric:       "Serviced.OpenFileDescriptors",
				MetricSource: "files",
				Name:         "Serviced Open File Descriptors",
				Rate:         false,
				Type:         "line",
			},
		},
		ID:     "serviced.ofd",
		Name:   "Serviced Open File Descriptors",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of serviced's total open file descriptors over time",
	}
}

//Major Page Faults
func newMajorPageFaultGraph(tags map[string][]string) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				ID:           "pgfault",
				Color:        "#aec7e8",
				Fill:         false,
				Format:       "%d",
				Legend:       "Major Page Faults",
				Metric:       "vmstat.pgmajfault",
				MetricSource: "virtual.memory",
				Name:         "Major Page Faults",
				Rate:         true,
				Type:         "line",
			},
		},
		ID:     "memory.major.pagefault",
		Name:   "Memory Major Page Faults",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "Faults / Min",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of major memory page faults over time",
	}
}

//Cpu Usage
func newCpuConfigGraph(tags map[string][]string, totalCores int) domain.GraphConfig {
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				Color:        "#aec7e8",
				Expression:   fmt.Sprintf("rpn:%d,/,100,*,60,/", totalCores),
				Fill:         false,
				Format:       "%6.2f",
				ID:           "system",
				Legend:       "CPU (System)",
				Metric:       "cpu.system",
				MetricSource: "cpu",
				Name:         "CPU (System)",
				Rate:         true,
				Type:         "line",
			},
			domain.DataPoint{
				Aggregator:   "avg",
				Color:        "#98df8a",
				Expression:   fmt.Sprintf("rpn:%d,/,100,*,60,/", totalCores),
				ID:           "user",
				Fill:         false,
				Format:       "%6.2f",
				Legend:       "CPU (User)",
				Metric:       "cpu.user",
				MetricSource: "cpu",
				Name:         "CPU (User)",
				Rate:         true,
				Type:         "line",
			},
		},
		ID:     "cpu.usage",
		Name:   "CPU Usage",
		Footer: false,
		Format: "%d",
		MinY:   &zero,
		MaxY:   &onehundred,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "% Used",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of system and user cpu usage over time",
	}
}

func newRSSConfigGraph(tags map[string][]string, totalMemory uint64) domain.GraphConfig {
	MaxY := int(totalMemory / 1024 / 1024 / 1024)
	return domain.GraphConfig{
		DataPoints: []domain.DataPoint{
			domain.DataPoint{
				Aggregator:   "avg",
				Expression:   "rpn:1024,/,1024,/,1024,/",
				Color:        "#aec7e8",
				Fill:         true,
				Format:       "%6.2f",
				Legend:       "Used (total - free)",
				Metric:       "memory.actualused",
				MetricSource: "memory",
				Name:         "Used",
				Type:         "area",
				ID:           "used",
			},
			domain.DataPoint{
				Aggregator:   "avg",
				Expression:   "rpn:1024,/,1024,/,1024,/",
				Color:        "#98df8a",
				Fill:         true,
				Format:       "%6.2f",
				Legend:       "Free (-buffers/+cached)",
				Metric:       "memory.actualfree",
				MetricSource: "memory",
				Name:         "Free",
				ID:           "Memory",
				Type:         "area",
			},
		},
		ID:     "memory.usage",
		Name:   "Memory Usage",
		Footer: false,
		Format: "%6.2f",
		MaxY:   &MaxY,
		MinY:   &zero,
		Range: &domain.GraphConfigRange{
			End:   "0s-ago",
			Start: "1h-ago",
		},
		YAxisLabel:  "GB",
		ReturnSet:   "EXACT",
		Type:        "line",
		DownSample:  "1m-avg",
		Tags:        tags,
		Description: "Graph of memory free (-buffers/+cache) vs used (total - free) over time",
	}
}

//newProfile builds a MonitoringProfile without graphs
func newProfile(tags map[string][]string) (domain.MonitorProfile, error) {
	p := domain.MonitorProfile{
		MetricConfigs: make([]domain.MetricConfig, len(profile.MetricConfigs)),
	}

	build, err := domain.NewMetricConfigBuilder("/metrics/api/performance/query", "POST")
	if err != nil {
		return p, err
	}

	//add metrics to profile
	for i := range profile.MetricConfigs {
		metricConfig := &profile.MetricConfigs[i]
		for j := range metricConfig.Metrics {
			metric := &metricConfig.Metrics[j]
			build.Metric(metric.ID, metric.Name).SetTags(tags)
		}

		config, err := build.Config(metricConfig.ID, metricConfig.Name, metricConfig.Description, "1h-ago")
		if err != nil {
			return p, err
		}
		p.MetricConfigs[i] = *config
	}
	return p, nil
}
<|endoftext|>


<commit_before>package gearcmd

import (
	"bufio"
	"bytes"
	"container/ring"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Clever/gearcmd/argsparser"
	"github.com/Clever/gearcmd/baseworker"
	"gopkg.in/Clever/kayvee-go.v2/logger"
)

// TaskConfig defines the configuration for the task.
type TaskConfig struct {
	FunctionName string
	FunctionCmd  string
	WarningLines int
	ParseArgs    bool
	CmdTimeout   time.Duration
	RetryCount   int
}

var (
	lg = logger.New("gearcmd")
	// legacy logger used to maintain existing alerts.
	// Once all workers are migrated over to using gearcmd, and the alarms are switched over,
	// then we can remove this logger
	legacyLg = logger.New("gearman")
)

// Process runs the Gearman job by running the configured task.
// We need to implement the Task interface so we return (byte[], error)
// though the byte[] is always nil.
func (conf TaskConfig) Process(job baseworker.Job) (b []byte, returnErr error) {
	jobID := getJobID(job)
	if jobID == "" {
		jobID = strconv.Itoa(rand.Int())
		lg.InfoD("rand-job-id", logger.M{"msg": "no job id parsed, random assigned."})
	}

	data := logger.M{
		"function": conf.FunctionName,
		"job_id":   jobID,
		"job_data": string(job.Data()),
	}

	// This wraps the actual processing to do some logging
	lg.InfoD("START", data)
	start := time.Now()

	for try := 0; try < conf.RetryCount+1; try++ {
		// We create a temporary directory to be used as the work directory of the process.
		// A new work directory is created for every retry of the process.
		// We try to use MEOS_SANDBOX, the default will be the system temp directory.
		tempDirPath, err := ioutil.TempDir(os.Getenv("MESOS_SANDBOX"),
			fmt.Sprintf("%s-%s-%d-", conf.FunctionName, jobID, try))
		if err != nil {
			lg.CriticalD("tempdir-failure", logger.M{"error": err.Error()})
			return nil, err
		}
		defer os.RemoveAll(tempDirPath)

		// insert the job id and the work directory path into the environment
		extraEnvVars := []string{
			fmt.Sprintf("JOB_ID=%s", jobID),
			fmt.Sprintf("WORK_DIR=%s", tempDirPath)}

		err = conf.doProcess(job, extraEnvVars)
		end := time.Now()
		data["type"] = "gauge"

		// Return if the job was successful.
		if err == nil {
			lg.InfoD("SUCCESS", logger.M{
				"type":     "counter",
				"function": conf.FunctionName})
			legacyLg.InfoD("success", logger.M{
				"type":     "counter",
				"function": conf.FunctionName})
			data["value"] = 1
			data["success"] = true
			lg.InfoD("END", data)
			// Hopefully none of our jobs last long enough for a uint64...
			lg.InfoD("duration", logger.M{
				"value":    uint64(end.Sub(start).Seconds() * 1000),
				"type":     "gauge",
				"function": conf.FunctionName})
			return nil, nil
		}

		data["value"] = 0
		data["success"] = false
		data["error_message"] = err.Error()
		returnErr = err

		if try != conf.RetryCount {
			lg.ErrorD("RETRY", data)
		}
	}

	lg.InfoD("FAILURE", logger.M{"type": "counter", "function": conf.FunctionName})
	lg.ErrorD("END", data)
	return nil, returnErr
}

// getJobID returns the jobId from the job handle
func getJobID(job baseworker.Job) string {
	splits := strings.Split(job.Handle(), ":")
	return splits[len(splits)-1]
}

func (conf TaskConfig) doProcess(job baseworker.Job, envVars []string) error {
	defer func() {
		// If we panicked then set the panic message as a warning. Gearman-go will
		// handle marking this job as failed.
		if r := recover(); r != nil {
			err := r.(error)
			job.SendWarning([]byte(err.Error()))
		}
	}()

	var args []string
	var err error
	if conf.ParseArgs {
		args, err = argsparser.ParseArgs(string(job.Data()))
		if err != nil {
			return fmt.Errorf("Failed to parse args: %s", err.Error())
		}
	} else {
		args = []string{string(job.Data())}
	}
	cmd := exec.Command(conf.FunctionCmd, args...)

	// insert provided env vars into the job
	cmd.Env = append(os.Environ(), envVars...)

	// create new pgid for this process so we can later kill all subprocess launched by it
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	// Write the stdout and stderr of the process to both this process' stdout and stderr
	// and also write it to a byte buffer so that we can return it with the Gearman job
	// data as necessary.
	var stderrbuf bytes.Buffer
	cmd.Stderr = io.MultiWriter(os.Stderr, &stderrbuf)
	defer sendStderrWarnings(&stderrbuf, job, conf.WarningLines)

	stdoutReader, stdoutWriter := io.Pipe()
	cmd.Stdout = io.MultiWriter(os.Stdout, stdoutWriter)

	done := make(chan error)
	go func() {
		defer close(done)

		finishedProcessingStdout := make(chan error)
		go func() {
			finishedProcessingStdout <- streamToGearman(stdoutReader, job)
		}()

		// Save the cmdErr. We want to process stdout and stderr before we return it
		cmdErr := cmd.Run()
		stdoutWriter.Close()

		stdoutErr := <-finishedProcessingStdout
		if cmdErr != nil {
			done <- cmdErr
		} else if stdoutErr != nil {
			done <- stdoutErr
		}
	}()
	// No timeout
	if conf.CmdTimeout == 0 {
		// Will be nil if the channel was closed without any errors
		return <-done
	}
	select {
	case err := <-done:
		// Will be nil if the channel was closed without any errors
		return err
	case <-time.After(conf.CmdTimeout):
		// kill entire group of process spawned by our cmd.Process
		pgid, err := syscall.Getpgid(cmd.Process.Pid)
		lg.InfoD("killing-pgid", logger.M{"pgid": pgid})
		if err != nil {
			return fmt.Errorf("process timeout after %s. Unable to get pgid, error: %s", conf.CmdTimeout.String(), err.Error())
		}
		// minus sign required to kill PGIDs
		// we use SIGTERM so that the subprocess can gracefully exit
		err = syscall.Kill(-pgid, syscall.SIGTERM)
		if err != nil {
			return fmt.Errorf("process timeout after %s. Unable to kill process, error: %s", conf.CmdTimeout.String(), err.Error())
		}
		return fmt.Errorf("process timed out after %s", conf.CmdTimeout.String())
	}
}

// This function streams the reader to the Gearman job (through job.SendData())
func streamToGearman(reader io.Reader, job baseworker.Job) error {
	buffer := make([]byte, 1024)
	for {
		n, err := reader.Read(buffer)
		// Process the data before processing the error (as per the io.Reader docs)
		if n > 0 {
			job.SendData(buffer[:n])
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

// sendStderrWarnings sends the last X lines in the stderr output and to the job's warnings
// field
func sendStderrWarnings(buffer io.Reader, job baseworker.Job, warningLines int) error {
	scanner := bufio.NewScanner(buffer)
	// Create a circular buffer for the last X lines
	lastStderrLines := ring.New(warningLines)
	for scanner.Scan() {
		lastStderrLines = lastStderrLines.Next()
		lastStderrLines.Value = scanner.Bytes()
	}
	// Walk forward through the buffer to get all the last X entries. Note that we call next first
	// so that we start at the oldest entry.
	for i := 0; i < lastStderrLines.Len(); i++ {
		if lastStderrLines = lastStderrLines.Next(); lastStderrLines.Value != nil {
			job.SendWarning(append(lastStderrLines.Value.([]byte), byte('\n')))
		}
	}
	return scanner.Err()
}
<commit_msg>Add failure legacy log<commit_after>package gearcmd

import (
	"bufio"
	"bytes"
	"container/ring"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Clever/gearcmd/argsparser"
	"github.com/Clever/gearcmd/baseworker"
	"gopkg.in/Clever/kayvee-go.v2/logger"
)

// TaskConfig defines the configuration for the task.
type TaskConfig struct {
	FunctionName string
	FunctionCmd  string
	WarningLines int
	ParseArgs    bool
	CmdTimeout   time.Duration
	RetryCount   int
}

var (
	lg = logger.New("gearcmd")
	// legacy logger used to maintain existing alerts.
	// Once all workers are migrated over to using gearcmd, and the alarms are switched over,
	// then we can remove this logger
	legacyLg = logger.New("gearman")
)

// Process runs the Gearman job by running the configured task.
// We need to implement the Task interface so we return (byte[], error)
// though the byte[] is always nil.
func (conf TaskConfig) Process(job baseworker.Job) (b []byte, returnErr error) {
	jobID := getJobID(job)
	if jobID == "" {
		jobID = strconv.Itoa(rand.Int())
		lg.InfoD("rand-job-id", logger.M{"msg": "no job id parsed, random assigned."})
	}

	data := logger.M{
		"function": conf.FunctionName,
		"job_id":   jobID,
		"job_data": string(job.Data()),
	}

	// This wraps the actual processing to do some logging
	lg.InfoD("START", data)
	start := time.Now()

	for try := 0; try < conf.RetryCount+1; try++ {
		// We create a temporary directory to be used as the work directory of the process.
		// A new work directory is created for every retry of the process.
		// We try to use MEOS_SANDBOX, the default will be the system temp directory.
		tempDirPath, err := ioutil.TempDir(os.Getenv("MESOS_SANDBOX"),
			fmt.Sprintf("%s-%s-%d-", conf.FunctionName, jobID, try))
		if err != nil {
			lg.CriticalD("tempdir-failure", logger.M{"error": err.Error()})
			return nil, err
		}
		defer os.RemoveAll(tempDirPath)

		// insert the job id and the work directory path into the environment
		extraEnvVars := []string{
			fmt.Sprintf("JOB_ID=%s", jobID),
			fmt.Sprintf("WORK_DIR=%s", tempDirPath)}

		err = conf.doProcess(job, extraEnvVars)
		end := time.Now()
		data["type"] = "gauge"

		// Return if the job was successful.
		if err == nil {
			lg.InfoD("SUCCESS", logger.M{
				"type":     "counter",
				"function": conf.FunctionName})
			legacyLg.InfoD("success", logger.M{
				"type":     "counter",
				"function": conf.FunctionName})
			data["value"] = 1
			data["success"] = true
			lg.InfoD("END", data)
			// Hopefully none of our jobs last long enough for a uint64...
			lg.InfoD("duration", logger.M{
				"value":    uint64(end.Sub(start).Seconds() * 1000),
				"type":     "gauge",
				"function": conf.FunctionName})
			return nil, nil
		}

		data["value"] = 0
		data["success"] = false
		data["error_message"] = err.Error()
		returnErr = err

		if try != conf.RetryCount {
			lg.ErrorD("RETRY", data)
		}
	}

	lg.InfoD("FAILURE", logger.M{"type": "counter", "function": conf.FunctionName})
	legacyLg.InfoD("failure", logger.M{"type": "counter", "function": conf.FunctionName})
	lg.ErrorD("END", data)
	return nil, returnErr
}

// getJobID returns the jobId from the job handle
func getJobID(job baseworker.Job) string {
	splits := strings.Split(job.Handle(), ":")
	return splits[len(splits)-1]
}

func (conf TaskConfig) doProcess(job baseworker.Job, envVars []string) error {
	defer func() {
		// If we panicked then set the panic message as a warning. Gearman-go will
		// handle marking this job as failed.
		if r := recover(); r != nil {
			err := r.(error)
			job.SendWarning([]byte(err.Error()))
		}
	}()

	var args []string
	var err error
	if conf.ParseArgs {
		args, err = argsparser.ParseArgs(string(job.Data()))
		if err != nil {
			return fmt.Errorf("Failed to parse args: %s", err.Error())
		}
	} else {
		args = []string{string(job.Data())}
	}
	cmd := exec.Command(conf.FunctionCmd, args...)

	// insert provided env vars into the job
	cmd.Env = append(os.Environ(), envVars...)

	// create new pgid for this process so we can later kill all subprocess launched by it
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	// Write the stdout and stderr of the process to both this process' stdout and stderr
	// and also write it to a byte buffer so that we can return it with the Gearman job
	// data as necessary.
	var stderrbuf bytes.Buffer
	cmd.Stderr = io.MultiWriter(os.Stderr, &stderrbuf)
	defer sendStderrWarnings(&stderrbuf, job, conf.WarningLines)

	stdoutReader, stdoutWriter := io.Pipe()
	cmd.Stdout = io.MultiWriter(os.Stdout, stdoutWriter)

	done := make(chan error)
	go func() {
		defer close(done)

		finishedProcessingStdout := make(chan error)
		go func() {
			finishedProcessingStdout <- streamToGearman(stdoutReader, job)
		}()

		// Save the cmdErr. We want to process stdout and stderr before we return it
		cmdErr := cmd.Run()
		stdoutWriter.Close()

		stdoutErr := <-finishedProcessingStdout
		if cmdErr != nil {
			done <- cmdErr
		} else if stdoutErr != nil {
			done <- stdoutErr
		}
	}()
	// No timeout
	if conf.CmdTimeout == 0 {
		// Will be nil if the channel was closed without any errors
		return <-done
	}
	select {
	case err := <-done:
		// Will be nil if the channel was closed without any errors
		return err
	case <-time.After(conf.CmdTimeout):
		// kill entire group of process spawned by our cmd.Process
		pgid, err := syscall.Getpgid(cmd.Process.Pid)
		lg.InfoD("killing-pgid", logger.M{"pgid": pgid})
		if err != nil {
			return fmt.Errorf("process timeout after %s. Unable to get pgid, error: %s", conf.CmdTimeout.String(), err.Error())
		}
		// minus sign required to kill PGIDs
		// we use SIGTERM so that the subprocess can gracefully exit
		err = syscall.Kill(-pgid, syscall.SIGTERM)
		if err != nil {
			return fmt.Errorf("process timeout after %s. Unable to kill process, error: %s", conf.CmdTimeout.String(), err.Error())
		}
		return fmt.Errorf("process timed out after %s", conf.CmdTimeout.String())
	}
}

// This function streams the reader to the Gearman job (through job.SendData())
func streamToGearman(reader io.Reader, job baseworker.Job) error {
	buffer := make([]byte, 1024)
	for {
		n, err := reader.Read(buffer)
		// Process the data before processing the error (as per the io.Reader docs)
		if n > 0 {
			job.SendData(buffer[:n])
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

// sendStderrWarnings sends the last X lines in the stderr output and to the job's warnings
// field
func sendStderrWarnings(buffer io.Reader, job baseworker.Job, warningLines int) error {
	scanner := bufio.NewScanner(buffer)
	// Create a circular buffer for the last X lines
	lastStderrLines := ring.New(warningLines)
	for scanner.Scan() {
		lastStderrLines = lastStderrLines.Next()
		lastStderrLines.Value = scanner.Bytes()
	}
	// Walk forward through the buffer to get all the last X entries. Note that we call next first
	// so that we start at the oldest entry.
	for i := 0; i < lastStderrLines.Len(); i++ {
		if lastStderrLines = lastStderrLines.Next(); lastStderrLines.Value != nil {
			job.SendWarning(append(lastStderrLines.Value.([]byte), byte('\n')))
		}
	}
	return scanner.Err()
}
<|endoftext|>