<commit_before>// Copyright 2018 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 ipcache

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
)

const (
	// DefaultAddressSpace is the address space used if none is provided.
	// TODO - once pkg/node adds this to clusterConfiguration, remove.
	DefaultAddressSpace = "default"
)

var (
	// IPIdentitiesPath is the path to where endpoint IPs are stored in the key-value
	//store.
	IPIdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "ip", "v1")

	// AddressSpace is the address space (cluster, etc.) in which policy is
	// computed. It is determined by the orchestration system / runtime.
	AddressSpace = DefaultAddressSpace

	// globalMap wraps the kvstore and provides reference-tracking for keys
	// that are upserted or released from the kvstore.
	globalMap *kvReferenceCounter

	setupIPIdentityWatcher sync.Once
)

// store is a key-value store for an underlying implementation, provided to
// mock out the kvstore for unit testing.
type store interface {
	// update will insert the {key, value} tuple into the underlying
	// kvstore.
	upsert(ctx context.Context, key string, value []byte, lease bool) error

	// delete will remove the key from the underlying kvstore.
	release(ctx context.Context, key string) error
}

// kvstoreImplementation is a store implementation backed by the kvstore.
type kvstoreImplementation struct{}

// upsert places the mapping of {key, value} into the kvstore, optionally with
// a lease.
func (k kvstoreImplementation) upsert(ctx context.Context, key string, value []byte, lease bool) error {
	_, err := kvstore.UpdateIfDifferent(ctx, key, value, lease)
	return err
}

// release removes the specified key from the kvstore.
func (k kvstoreImplementation) release(ctx context.Context, key string) error {
	return kvstore.Delete(key)
}

// kvReferenceCounter provides a thin wrapper around the kvstore which adds
// reference tracking for all entries being updated. When the first key is
// updated, it adds a reference to the kvstore and tracks the reference
// internally. Subsequent updates also update the kvstore, and add a reference.
// Deletes from the kvReferenceCounter are only propagated to the kvstore when
// the final reference is released.
//
// This has some small overlap with the pkg/kvstore/allocator but this is only
// a map from key to reference count rather than also tracking values.
type kvReferenceCounter struct {
	lock.Mutex
	store

	// keys is a map from key to reference count for locally-referenced
	// keys in the global kvstore.
	keys map[string]uint64

	// marshaledIPIDPair is map indexed by the key that contains the
	// marshaled IPIdentityPair
	marshaledIPIDPairs map[string][]byte
}

// newKVReferenceCounter creates a new reference counter using the specified
// store as the underlying location for key/value pairs to be stored.
func newKVReferenceCounter(s store) *kvReferenceCounter {
	return &kvReferenceCounter{
		store:              s,
		keys:               map[string]uint64{},
		marshaledIPIDPairs: map[string][]byte{},
	}
}

// upsert attempts to insert the specified {key, ipIDPair} into the kvstore. If
// the key has previously been upserted, increments a reference on the key.
// Always updates the underlying store with this {key, ipIDPair} tuple.
// Only adds a reference to the key if the upsert is successful.
func (r *kvReferenceCounter) upsert(ctx context.Context, ipKey string, ipIDPair identity.IPIdentityPair) error {
	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.IPMask:       ipIDPair.Mask,
		logfields.Identity:     ipIDPair.ID,
		logfields.Modification: Upsert,
	}).Debug("upserting CIDR->ID mapping to kvstore")

	r.Lock()
	defer r.Unlock()
	refcnt := r.keys[ipKey] // 0 if not found
	refcnt++
	err = r.store.upsert(ctx, ipKey, marshaledIPIDPair, true)
	if err == nil {
		r.keys[ipKey] = refcnt
		r.marshaledIPIDPairs[ipKey] = marshaledIPIDPair
	}
	return err
}

// release removes a reference to the specified key. If the number of
// references reaches 0, the key is removed from the underlying kvstore.
func (r *kvReferenceCounter) release(ctx context.Context, key string) (err error) {
	r.Lock()
	defer r.Unlock()

	refcnt, ok := r.keys[key] // 0 if not found
	// avoid underflow and report bug
	if !ok || refcnt == 0 {
		log.WithField("key", key).Error("BUG: attempt to release ipcache entry while refcnt == 0")
		return nil
	}

	refcnt--
	if refcnt == 0 {
		delete(r.keys, key)
		delete(r.marshaledIPIDPairs, key)
		err = r.store.release(ctx, key)
	} else {
		r.keys[key] = refcnt
	}
	return err
}

// UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the
// kvstore, which will subsequently trigger an event in NewIPIdentityWatcher().
func UpsertIPToKVStore(ctx context.Context, IP, hostIP net.IP, ID identity.NumericIdentity, key uint8, metadata string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, IP.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       IP,
		ID:       ID,
		Metadata: metadata,
		HostIP:   hostIP,
		Key:      key,
	}

	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.Identity:     ipIDPair.ID,
		logfields.Key:          ipIDPair.Key,
		logfields.Modification: Upsert,
	}).Debug("upserting IP->ID mapping to kvstore")

	return globalMap.store.upsert(ctx, ipKey, marshaledIPIDPair, true)
}

// keyToIPNet returns the IPNet describing the key, whether it is a host, and
// an error (if one occurs)
func keyToIPNet(key string) (parsedPrefix *net.IPNet, host bool, err error) {
	requiredPrefix := fmt.Sprintf("%s/", path.Join(IPIdentitiesPath, AddressSpace))
	if !strings.HasPrefix(key, requiredPrefix) {
		err = fmt.Errorf("Found invalid key %s outside of prefix %s", key, IPIdentitiesPath)
		return
	}

	suffix := strings.TrimPrefix(key, requiredPrefix)

	// Key is formatted as "prefix/192.0.2.0/24" for CIDRs
	_, parsedPrefix, err = net.ParseCIDR(suffix)
	if err != nil {
		// Key is likely a host in the format "prefix/192.0.2.3"
		parsedIP := net.ParseIP(suffix)
		if parsedIP == nil {
			err = fmt.Errorf("unable to parse IP from suffix %s", suffix)
			return
		}
		err = nil
		host = true
		ipv4 := parsedIP.To4()
		bits := net.IPv6len * 8
		if ipv4 != nil {
			parsedIP = ipv4
			bits = net.IPv4len * 8
		}
		parsedPrefix = &net.IPNet{IP: parsedIP, Mask: net.CIDRMask(bits, bits)}
	}

	return
}

// DeleteIPFromKVStore removes the IP->Identity mapping for the specified ip
// from the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func DeleteIPFromKVStore(ctx context.Context, ip string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, ip)
	return globalMap.store.release(ctx, ipKey)
}

// IPIdentityWatcher is a watcher that will notify when IP<->identity mappings
// change in the kvstore
type IPIdentityWatcher struct {
	backend  kvstore.BackendOperations
	stop     chan struct{}
	synced   chan struct{}
	stopOnce sync.Once
}

// NewIPIdentityWatcher creates a new IPIdentityWatcher using the specified
// kvstore backend
func NewIPIdentityWatcher(backend kvstore.BackendOperations) *IPIdentityWatcher {
	watcher := &IPIdentityWatcher{
		backend: backend,
		stop:    make(chan struct{}),
		synced:  make(chan struct{}),
	}

	return watcher
}

// Watch starts the watcher and blocks waiting for events. When events are
// received from the kvstore, All IPIdentityMappingListener are notified. The
// function returns when IPIdentityWatcher.Close() is called. The watcher will
// automatically restart as required.
func (iw *IPIdentityWatcher) Watch() {
restart:
	watcher := iw.backend.ListAndWatch("endpointIPWatcher", IPIdentitiesPath, 512)

	for {
		select {
		// Get events from channel as they come in.
		case event, ok := <-watcher.Events:
			if !ok {
				log.Debugf("%s closed, restarting watch", watcher.String())
				time.Sleep(500 * time.Millisecond)
				goto restart
			}

			scopedLog := log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key})
			scopedLog.Debug("Received event")

			// Synchronize local caching of endpoint IP to ipIDPair mapping with
			// operation key-value store has informed us about.
			//
			// To resolve conflicts between hosts and full CIDR prefixes:
			// - Insert hosts into the cache as ".../w.x.y.z"
			// - Insert CIDRS into the cache as ".../w.x.y.z/N"
			// - If a host entry created, notify the listeners.
			// - If a CIDR is created and there's no overlapping host
			//   entry, ie it is a less than fully masked CIDR, OR
			//   it is a fully masked CIDR and there is no corresponding
			//   host entry, then:
			//   - Notify the listeners.
			//   - Otherwise, do not notify listeners.
			// - If a host is removed, check for an overlapping CIDR
			//   and if it exists, notify the listeners with an upsert
			//   for the CIDR's identity
			// - If any other deletion case, notify listeners of
			//   the deletion event.
			switch event.Typ {
			case kvstore.EventTypeListDone:
				IPIdentityCache.Lock()
				for _, listener := range IPIdentityCache.listeners {
					listener.OnIPIdentityCacheGC()
				}
				IPIdentityCache.Unlock()
				close(iw.synced)

			case kvstore.EventTypeCreate, kvstore.EventTypeModify:
				var ipIDPair identity.IPIdentityPair
				err := json.Unmarshal(event.Value, &ipIDPair)
				if err != nil {
					scopedLog.WithError(err).Errorf("Not adding entry to ip cache; error unmarshaling data from key-value store")
					continue
				}
				ip := ipIDPair.PrefixString()
				if ip == "<nil>" {
					scopedLog.Debug("Ignoring entry with nil IP")
					continue
				}

				IPIdentityCache.Upsert(ipIDPair.PrefixString(), ipIDPair.HostIP, ipIDPair.Key, Identity{
					ID:     ipIDPair.ID,
					Source: FromKVStore,
				})

			case kvstore.EventTypeDelete:
				// Value is not present in deletion event;
				// need to convert kvstore key to IP.
				ipnet, isHost, err := keyToIPNet(event.Key)
				if err != nil {
					scopedLog.WithError(err).Error("Error parsing IP from key")
					continue
				}
				var ip string
				if isHost {
					ip = ipnet.IP.String()
				} else {
					ip = ipnet.String()
				}
				globalMap.Lock()

				if m, ok := globalMap.marshaledIPIDPairs[event.Key]; ok {
					log.WithField("ip", ip).Warning("Received kvstore delete notification for alive ipcache entry")
					err := globalMap.store.upsert(context.TODO(), event.Key, m, true)
					if err != nil {
						log.WithError(err).WithField("ip", ip).Warning("Unable to re-create alive ipcache entry")
					}
					globalMap.Unlock()
				} else if _, ok := globalMap.keys[path.Join(IPIdentitiesPath, AddressSpace, ip)]; ok {
					log.WithField("ip", ip).Warning("Received kvstore delete notification with mismatching key but alive IP. Ignoring")
					globalMap.Unlock()
				} else {
					globalMap.Unlock()

					// The key no longer exists in the
					// local cache, it is safe to remove
					// from the datapath ipcache.
					IPIdentityCache.Delete(ip, FromKVStore)
				}
			}

		case <-iw.stop:
			// identity watcher was stopped
			watcher.Stop()
			return
		}
	}
}

// Close stops the IPIdentityWatcher and causes Watch() to return
func (iw *IPIdentityWatcher) Close() {
	iw.stopOnce.Do(func() {
		close(iw.stop)
	})
}

func (iw *IPIdentityWatcher) waitForInitialSync() {
	<-iw.synced
}

var (
	watcher     *IPIdentityWatcher
	initialized = make(chan struct{}, 0)
)

// InitIPIdentityWatcher initializes the watcher for ip-identity mapping events
// in the key-value store.
func InitIPIdentityWatcher() {
	setupIPIdentityWatcher.Do(func() {
		globalMap = newKVReferenceCounter(kvstoreImplementation{})
		go func() {
			log.Info("Starting IP identity watcher")
			watcher = NewIPIdentityWatcher(kvstore.Client())
			close(initialized)
			watcher.Watch()
		}()
	})
}

// WaitForInitialSync waits until the ipcache has been synchronized from the kvstore
func WaitForInitialSync() {
	<-initialized
	watcher.waitForInitialSync()
}
<commit_msg>pkg/ipcache: initialize globalmap at import time<commit_after>// Copyright 2018 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 ipcache

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
)

const (
	// DefaultAddressSpace is the address space used if none is provided.
	// TODO - once pkg/node adds this to clusterConfiguration, remove.
	DefaultAddressSpace = "default"
)

var (
	// IPIdentitiesPath is the path to where endpoint IPs are stored in the key-value
	//store.
	IPIdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "ip", "v1")

	// AddressSpace is the address space (cluster, etc.) in which policy is
	// computed. It is determined by the orchestration system / runtime.
	AddressSpace = DefaultAddressSpace

	// globalMap wraps the kvstore and provides reference-tracking for keys
	// that are upserted or released from the kvstore.
	globalMap = newKVReferenceCounter(kvstoreImplementation{})

	setupIPIdentityWatcher sync.Once
)

// store is a key-value store for an underlying implementation, provided to
// mock out the kvstore for unit testing.
type store interface {
	// update will insert the {key, value} tuple into the underlying
	// kvstore.
	upsert(ctx context.Context, key string, value []byte, lease bool) error

	// delete will remove the key from the underlying kvstore.
	release(ctx context.Context, key string) error
}

// kvstoreImplementation is a store implementation backed by the kvstore.
type kvstoreImplementation struct{}

// upsert places the mapping of {key, value} into the kvstore, optionally with
// a lease.
func (k kvstoreImplementation) upsert(ctx context.Context, key string, value []byte, lease bool) error {
	_, err := kvstore.UpdateIfDifferent(ctx, key, value, lease)
	return err
}

// release removes the specified key from the kvstore.
func (k kvstoreImplementation) release(ctx context.Context, key string) error {
	return kvstore.Delete(key)
}

// kvReferenceCounter provides a thin wrapper around the kvstore which adds
// reference tracking for all entries being updated. When the first key is
// updated, it adds a reference to the kvstore and tracks the reference
// internally. Subsequent updates also update the kvstore, and add a reference.
// Deletes from the kvReferenceCounter are only propagated to the kvstore when
// the final reference is released.
//
// This has some small overlap with the pkg/kvstore/allocator but this is only
// a map from key to reference count rather than also tracking values.
type kvReferenceCounter struct {
	lock.Mutex
	store

	// keys is a map from key to reference count for locally-referenced
	// keys in the global kvstore.
	keys map[string]uint64

	// marshaledIPIDPair is map indexed by the key that contains the
	// marshaled IPIdentityPair
	marshaledIPIDPairs map[string][]byte
}

// newKVReferenceCounter creates a new reference counter using the specified
// store as the underlying location for key/value pairs to be stored.
func newKVReferenceCounter(s store) *kvReferenceCounter {
	return &kvReferenceCounter{
		store:              s,
		keys:               map[string]uint64{},
		marshaledIPIDPairs: map[string][]byte{},
	}
}

// upsert attempts to insert the specified {key, ipIDPair} into the kvstore. If
// the key has previously been upserted, increments a reference on the key.
// Always updates the underlying store with this {key, ipIDPair} tuple.
// Only adds a reference to the key if the upsert is successful.
func (r *kvReferenceCounter) upsert(ctx context.Context, ipKey string, ipIDPair identity.IPIdentityPair) error {
	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.IPMask:       ipIDPair.Mask,
		logfields.Identity:     ipIDPair.ID,
		logfields.Modification: Upsert,
	}).Debug("upserting CIDR->ID mapping to kvstore")

	r.Lock()
	defer r.Unlock()
	refcnt := r.keys[ipKey] // 0 if not found
	refcnt++
	err = r.store.upsert(ctx, ipKey, marshaledIPIDPair, true)
	if err == nil {
		r.keys[ipKey] = refcnt
		r.marshaledIPIDPairs[ipKey] = marshaledIPIDPair
	}
	return err
}

// release removes a reference to the specified key. If the number of
// references reaches 0, the key is removed from the underlying kvstore.
func (r *kvReferenceCounter) release(ctx context.Context, key string) (err error) {
	r.Lock()
	defer r.Unlock()

	refcnt, ok := r.keys[key] // 0 if not found
	// avoid underflow and report bug
	if !ok || refcnt == 0 {
		log.WithField("key", key).Error("BUG: attempt to release ipcache entry while refcnt == 0")
		return nil
	}

	refcnt--
	if refcnt == 0 {
		delete(r.keys, key)
		delete(r.marshaledIPIDPairs, key)
		err = r.store.release(ctx, key)
	} else {
		r.keys[key] = refcnt
	}
	return err
}

// UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the
// kvstore, which will subsequently trigger an event in NewIPIdentityWatcher().
func UpsertIPToKVStore(ctx context.Context, IP, hostIP net.IP, ID identity.NumericIdentity, key uint8, metadata string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, IP.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       IP,
		ID:       ID,
		Metadata: metadata,
		HostIP:   hostIP,
		Key:      key,
	}

	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.Identity:     ipIDPair.ID,
		logfields.Key:          ipIDPair.Key,
		logfields.Modification: Upsert,
	}).Debug("upserting IP->ID mapping to kvstore")

	return globalMap.store.upsert(ctx, ipKey, marshaledIPIDPair, true)
}

// keyToIPNet returns the IPNet describing the key, whether it is a host, and
// an error (if one occurs)
func keyToIPNet(key string) (parsedPrefix *net.IPNet, host bool, err error) {
	requiredPrefix := fmt.Sprintf("%s/", path.Join(IPIdentitiesPath, AddressSpace))
	if !strings.HasPrefix(key, requiredPrefix) {
		err = fmt.Errorf("Found invalid key %s outside of prefix %s", key, IPIdentitiesPath)
		return
	}

	suffix := strings.TrimPrefix(key, requiredPrefix)

	// Key is formatted as "prefix/192.0.2.0/24" for CIDRs
	_, parsedPrefix, err = net.ParseCIDR(suffix)
	if err != nil {
		// Key is likely a host in the format "prefix/192.0.2.3"
		parsedIP := net.ParseIP(suffix)
		if parsedIP == nil {
			err = fmt.Errorf("unable to parse IP from suffix %s", suffix)
			return
		}
		err = nil
		host = true
		ipv4 := parsedIP.To4()
		bits := net.IPv6len * 8
		if ipv4 != nil {
			parsedIP = ipv4
			bits = net.IPv4len * 8
		}
		parsedPrefix = &net.IPNet{IP: parsedIP, Mask: net.CIDRMask(bits, bits)}
	}

	return
}

// DeleteIPFromKVStore removes the IP->Identity mapping for the specified ip
// from the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func DeleteIPFromKVStore(ctx context.Context, ip string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, ip)
	return globalMap.store.release(ctx, ipKey)
}

// IPIdentityWatcher is a watcher that will notify when IP<->identity mappings
// change in the kvstore
type IPIdentityWatcher struct {
	backend  kvstore.BackendOperations
	stop     chan struct{}
	synced   chan struct{}
	stopOnce sync.Once
}

// NewIPIdentityWatcher creates a new IPIdentityWatcher using the specified
// kvstore backend
func NewIPIdentityWatcher(backend kvstore.BackendOperations) *IPIdentityWatcher {
	watcher := &IPIdentityWatcher{
		backend: backend,
		stop:    make(chan struct{}),
		synced:  make(chan struct{}),
	}

	return watcher
}

// Watch starts the watcher and blocks waiting for events. When events are
// received from the kvstore, All IPIdentityMappingListener are notified. The
// function returns when IPIdentityWatcher.Close() is called. The watcher will
// automatically restart as required.
func (iw *IPIdentityWatcher) Watch() {
restart:
	watcher := iw.backend.ListAndWatch("endpointIPWatcher", IPIdentitiesPath, 512)

	for {
		select {
		// Get events from channel as they come in.
		case event, ok := <-watcher.Events:
			if !ok {
				log.Debugf("%s closed, restarting watch", watcher.String())
				time.Sleep(500 * time.Millisecond)
				goto restart
			}

			scopedLog := log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key})
			scopedLog.Debug("Received event")

			// Synchronize local caching of endpoint IP to ipIDPair mapping with
			// operation key-value store has informed us about.
			//
			// To resolve conflicts between hosts and full CIDR prefixes:
			// - Insert hosts into the cache as ".../w.x.y.z"
			// - Insert CIDRS into the cache as ".../w.x.y.z/N"
			// - If a host entry created, notify the listeners.
			// - If a CIDR is created and there's no overlapping host
			//   entry, ie it is a less than fully masked CIDR, OR
			//   it is a fully masked CIDR and there is no corresponding
			//   host entry, then:
			//   - Notify the listeners.
			//   - Otherwise, do not notify listeners.
			// - If a host is removed, check for an overlapping CIDR
			//   and if it exists, notify the listeners with an upsert
			//   for the CIDR's identity
			// - If any other deletion case, notify listeners of
			//   the deletion event.
			switch event.Typ {
			case kvstore.EventTypeListDone:
				IPIdentityCache.Lock()
				for _, listener := range IPIdentityCache.listeners {
					listener.OnIPIdentityCacheGC()
				}
				IPIdentityCache.Unlock()
				close(iw.synced)

			case kvstore.EventTypeCreate, kvstore.EventTypeModify:
				var ipIDPair identity.IPIdentityPair
				err := json.Unmarshal(event.Value, &ipIDPair)
				if err != nil {
					scopedLog.WithError(err).Errorf("Not adding entry to ip cache; error unmarshaling data from key-value store")
					continue
				}
				ip := ipIDPair.PrefixString()
				if ip == "<nil>" {
					scopedLog.Debug("Ignoring entry with nil IP")
					continue
				}

				IPIdentityCache.Upsert(ipIDPair.PrefixString(), ipIDPair.HostIP, ipIDPair.Key, Identity{
					ID:     ipIDPair.ID,
					Source: FromKVStore,
				})

			case kvstore.EventTypeDelete:
				// Value is not present in deletion event;
				// need to convert kvstore key to IP.
				ipnet, isHost, err := keyToIPNet(event.Key)
				if err != nil {
					scopedLog.WithError(err).Error("Error parsing IP from key")
					continue
				}
				var ip string
				if isHost {
					ip = ipnet.IP.String()
				} else {
					ip = ipnet.String()
				}
				globalMap.Lock()

				if m, ok := globalMap.marshaledIPIDPairs[event.Key]; ok {
					log.WithField("ip", ip).Warning("Received kvstore delete notification for alive ipcache entry")
					err := globalMap.store.upsert(context.TODO(), event.Key, m, true)
					if err != nil {
						log.WithError(err).WithField("ip", ip).Warning("Unable to re-create alive ipcache entry")
					}
					globalMap.Unlock()
				} else if _, ok := globalMap.keys[path.Join(IPIdentitiesPath, AddressSpace, ip)]; ok {
					log.WithField("ip", ip).Warning("Received kvstore delete notification with mismatching key but alive IP. Ignoring")
					globalMap.Unlock()
				} else {
					globalMap.Unlock()

					// The key no longer exists in the
					// local cache, it is safe to remove
					// from the datapath ipcache.
					IPIdentityCache.Delete(ip, FromKVStore)
				}
			}

		case <-iw.stop:
			// identity watcher was stopped
			watcher.Stop()
			return
		}
	}
}

// Close stops the IPIdentityWatcher and causes Watch() to return
func (iw *IPIdentityWatcher) Close() {
	iw.stopOnce.Do(func() {
		close(iw.stop)
	})
}

func (iw *IPIdentityWatcher) waitForInitialSync() {
	<-iw.synced
}

var (
	watcher     *IPIdentityWatcher
	initialized = make(chan struct{}, 0)
)

// InitIPIdentityWatcher initializes the watcher for ip-identity mapping events
// in the key-value store.
func InitIPIdentityWatcher() {
	setupIPIdentityWatcher.Do(func() {
		go func() {
			log.Info("Starting IP identity watcher")
			watcher = NewIPIdentityWatcher(kvstore.Client())
			close(initialized)
			watcher.Watch()
		}()
	})
}

// WaitForInitialSync waits until the ipcache has been synchronized from the kvstore
func WaitForInitialSync() {
	<-initialized
	watcher.waitForInitialSync()
}
<|endoftext|>


<commit_before>package metrics

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/units"
	"github.com/ericchiang/k8s"
	apiv1 "github.com/ericchiang/k8s/api/v1"
	"github.com/matt-deboer/kuill/pkg/auth"
	"github.com/matt-deboer/kuill/pkg/helpers"
	log "github.com/sirupsen/logrus"
)

const serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"

// Provider gathers metrics from the underlying kubernetes cluster
type Provider struct {
	summary     *Summaries
	mutex       sync.RWMutex
	client      *k8s.Client
	killSwitch  chan struct{}
	bearerToken string
}

// NewMetricsProvider returns a Provider capable of returning metrics for the cluster
func NewMetricsProvider(kubeconfig string) (*Provider, error) {

	client, bearerToken, err := helpers.NewKubeClient(kubeconfig)
	if err != nil {
		return nil, err
	}

	m := &Provider{
		client:      client,
		killSwitch:  make(chan struct{}, 1),
		bearerToken: string(bearerToken),
	}

	m.summary = m.summarize()

	go func() {
		for {
			select {
			case _ = <-m.killSwitch:
				return
			default:
				time.Sleep(15 * time.Second)

				summary := m.summarize()

				m.mutex.Lock()
				m.summary = summary
				m.mutex.Unlock()
			}
		}
	}()

	return m, nil
}

// Close shuts down the metrics provider's background worker(s)
func (m *Provider) Close() {
	m.killSwitch <- struct{}{}
}

// GetMetrics returns a summarized metrics bundle for the entire cluster
func (m *Provider) GetMetrics(w http.ResponseWriter, r *http.Request, context auth.Context) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	data, err := json.Marshal(m.summary)
	if err != nil {
		log.Error(err)
		http.Error(w, "Failed to marshall metrics", http.StatusInternalServerError)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.Write(data)
	}
}

func (m *Provider) summarize() *Summaries {

	summary := &Summaries{
		Namespace: make(map[string]*Summary),
		Node:      make(map[string]*Summary),
		Pod:       make(map[string]*Summary),
	}
	aggregates := make(map[string]uint64)
	namespaces := make(map[string]bool)

	for _, node := range m.listNodes() {
		nodeName := *node.Metadata.Name
		nodeSummary := m.readNodeSummary(fmt.Sprintf("%s/api/v1/proxy/nodes/%s:10255/stats/summary", m.client.Endpoint, nodeName))
		if nodeSummary != nil {

			convertedSummary, convertedByNs := convertSummary(nodeSummary, node, summary)
			// aggregate at the cluster level
			aggregates["Cluster:totalMillicores"] += convertedSummary.CPU.Total
			aggregates["Cluster:memTotalBytes"] += convertedSummary.Memory.Total
			aggregates["Cluster:usageNanoCores"] += nodeSummary.Node.CPU.UsageNanoCores
			aggregates["Cluster:memUsageBytes"] += nodeSummary.Node.Memory.UsageBytes
			aggregates["Cluster:networkTxBytes"] += nodeSummary.Node.Network.TxBytes
			aggregates["Cluster:networkRxBytes"] += nodeSummary.Node.Network.RxBytes
			aggregates["Cluster:networkSeconds"] += uint64(nodeSummary.Node.Network.Time.Sub(nodeSummary.Node.StartTime).Seconds())
			aggregates["Cluster:fsCapacityBytes"] += nodeSummary.Node.Fs.CapacityBytes
			aggregates["Cluster:fsUsedBytes"] += nodeSummary.Node.Fs.UsedBytes

			// aggregate at the namespace level
			for ns, nsStat := range convertedByNs {
				aggregates["Namespace:"+ns+":fsUsedBytes"] += nsStat.Disk.Usage
				aggregates["Namespace:"+ns+":fsCapacityBytes"] += nsStat.Disk.Total
			}

			for _, podStats := range nodeSummary.Pods {
				ns := podStats.PodRef.Namespace
				aggregates["Namespace:"+ns+":pods"]++
				aggregates["Cluster:pods"]++
				convertedSummary.Pods.Usage++
				namespaces[ns] = true
				for _, volStats := range podStats.VolumeStats {
					aggregates["Cluster:volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Cluster:volUsedBytes"] += volStats.UsedBytes
					aggregates["Namespace:"+ns+":volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Namespace:"+ns+":volUsedBytes"] += volStats.UsedBytes
				}
				for _, c := range podStats.Containers {
					aggregates["Namespace:"+ns+":containers"]++
					aggregates["Cluster:containers"]++
					convertedSummary.Containers.Usage++
					if c.CPU != nil {
						aggregates["Namespace:"+ns+":usageCoreNanoSeconds"] += c.CPU.UsageCoreNanoSeconds
						aggregates["Namespace:"+ns+":usageNanoCores"] += c.CPU.UsageNanoCores
					}
					if c.Memory != nil {
						aggregates["Namespace:"+ns+":memAvailableBytes"] += c.Memory.AvailableBytes
						aggregates["Namespace:"+ns+":memUsageBytes"] += c.Memory.UsageBytes
						aggregates["Namespace:"+ns+":memRSSBytes"] += c.Memory.RSSBytes
						aggregates["Namespace:"+ns+":memWorkingSetBytes"] += c.Memory.WorkingSetBytes
					}
				}
				if podStats.Network != nil {
					aggregates["Namespace:"+ns+":networkTxBytes"] += podStats.Network.TxBytes
					aggregates["Namespace:"+ns+":networkRxBytes"] += podStats.Network.RxBytes
					aggregates["Namespace:"+ns+":networkSeconds"] += uint64(podStats.Network.Time.Unix() - podStats.StartTime.Unix())
				}
			}
			summary.Node[*node.Metadata.Name] = convertedSummary
		}
	}

	summary.Cluster = makeSummary("Cluster", aggregates)
	for ns := range namespaces {
		prefix := "Namespace:" + ns
		nsSummary := makeSummary(prefix, aggregates)

		// To be replaced by resource quota for the NS, should one exist
		nsSummary.CPU.Total = summary.Cluster.CPU.Total
		nsSummary.CPU.Ratio = float64(nsSummary.CPU.Usage) / float64(nsSummary.CPU.Total)
		nsSummary.Memory.Total = summary.Cluster.Memory.Total
		nsSummary.Memory.Ratio = float64(nsSummary.Memory.Usage) / float64(nsSummary.Memory.Total)

		summary.Namespace[ns] = nsSummary
	}

	return summary
}

func safeGet(ref *uint64) uint64 {
	if ref != nil {
		return *ref
	}
	return 0
}

func convertSummary(summary *KubeletStatsSummary, node *apiv1.Node, summaries *Summaries) (*Summary, map[string]*Summary) {

	summaryByNs := make(map[string]*Summary)
	networkSeconds := uint64(summary.Node.Network.Time.Unix() - summary.Node.StartTime.Unix())
	volCapacityBytes := uint64(0)
	volUsageBytes := uint64(0)
	for _, podStats := range summary.Pods {

		podSummary := convertPodSummary(podStats)
		podKey := fmt.Sprintf("%s/%s", podStats.PodRef.Namespace, podStats.PodRef.Name)
		summaries.Pod[podKey] = podSummary
		nsSummary, ok := summaryByNs[podStats.PodRef.Namespace]
		if !ok {
			nsSummary = &Summary{Disk: &SummaryStat{Units: "bytes"}}
			summaryByNs[podStats.PodRef.Namespace] = nsSummary
		}
		nsSummary.Disk.Usage += podSummary.Disk.Usage
		nsSummary.Disk.Total += podSummary.Disk.Total

		volUsageBytes += podSummary.Volumes.Usage
		volCapacityBytes += podSummary.Volumes.Total
	}

	totalCPUCores, err := strconv.Atoi(*node.Status.Allocatable["cpu"].String_)
	if err != nil {
		log.Errorf("Failed to parse allocatable.cpu; %v", err)
	}
	allocatableBytesString := *node.Status.Allocatable["memory"].String_
	if strings.HasSuffix(allocatableBytesString, "i") {
		allocatableBytesString += "B"
	}
	totalMemoryBytes, err := units.ParseStrictBytes(allocatableBytesString)
	if err != nil {
		log.Errorf("Failed to parse allocatable.memory; %v", err)
	}

	if summary.Node.CPU == nil {
		summary.Node.CPU = &CPUStats{}
	}

	if summary.Node.Memory == nil {
		summary.Node.Memory = &MemoryStats{}
	}

	if summary.Node.Fs == nil {
		summary.Node.Fs = &FsStats{}
	}

	return &Summary{
		CPU: newSummaryStat(
			summary.Node.CPU.UsageNanoCores/1000000,
			uint64(totalCPUCores*1000),
			"millicores"),
		Memory: newSummaryStat(
			summary.Node.Memory.UsageBytes,
			uint64(totalMemoryBytes),
			"bytes"),
		Disk: newSummaryStat(
			summary.Node.Fs.UsedBytes,
			summary.Node.Fs.CapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsageBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			summary.Node.Network.RxBytes/networkSeconds,
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			summary.Node.Network.TxBytes/networkSeconds,
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 0,
		},
		Containers: &SummaryStat{
			Usage: 0,
		},
	}, summaryByNs
}

func convertPodSummary(pod PodStats) *Summary {

	networkSeconds := uint64(pod.Network.Time.Unix() - pod.StartTime.Unix())
	summary := &Summary{
		CPU:     &SummaryStat{Units: "millicores"},
		Memory:  &SummaryStat{Units: "bytes"},
		Volumes: &SummaryStat{Units: "bytes"},
		Disk:    &SummaryStat{Units: "bytes"},
		NetRx: newSummaryStat(
			pod.Network.RxBytes/networkSeconds,
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			pod.Network.TxBytes/networkSeconds,
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 1,
		},
		Containers: &SummaryStat{
			Usage: uint64(len(pod.Containers)),
		},
	}

	for _, volStats := range pod.VolumeStats {
		summary.Volumes.Usage += volStats.CapacityBytes
		summary.Volumes.Total += volStats.UsedBytes
	}

	for _, container := range pod.Containers {
		if container.CPU != nil {
			summary.CPU.Usage += container.CPU.UsageNanoCores / 1000000
		}
		if container.Memory != nil {
			summary.Memory.Usage += container.Memory.UsageBytes
		}
		if container.Rootfs != nil {
			summary.Disk.Usage += container.Rootfs.UsedBytes
		}
	}
	return summary
}

func makeSummary(prefix string, aggregates map[string]uint64) *Summary {

	usageNanoCores := aggregates[prefix+":usageNanoCores"]
	totalMillicores := aggregates[prefix+":totalMillicores"]
	memUsageBytes := aggregates[prefix+":memUsageBytes"]
	memTotalBytes := aggregates[prefix+":memTotalBytes"]
	networkTxBytes := aggregates[prefix+":networkTxBytes"]
	networkRxBytes := aggregates[prefix+":networkRxBytes"]
	// We create a fake timespan here with the current time as the endpoint;
	// the important part is that the duration is correct
	networkSeconds := aggregates[prefix+":networkSeconds"]
	if networkSeconds == 0 {
		networkSeconds = math.MaxUint64
	}

	fsCapacityBytes := aggregates[prefix+":fsCapacityBytes"]
	fsUsedBytes := aggregates[prefix+":fsUsedBytes"]
	volCapacityBytes := aggregates[prefix+":volCapacityBytes"]
	volUsedBytes := aggregates[prefix+":volUsedBytes"]
	podCount := aggregates[prefix+":pods"]
	containerCount := aggregates[prefix+":containers"]

	return &Summary{

		CPU: newSummaryStat(
			usageNanoCores/1000000,
			totalMillicores,
			"millicores"),
		Memory: newSummaryStat(
			memUsageBytes,
			memTotalBytes,
			"bytes"),
		Disk: newSummaryStat(
			fsUsedBytes,
			fsCapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsedBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			networkRxBytes/networkSeconds,
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			networkTxBytes/networkSeconds,
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: podCount,
		},
		Containers: &SummaryStat{
			Usage: containerCount,
		},
	}
}

func (m *Provider) readNodeSummary(path string) *KubeletStatsSummary {

	req, err := http.NewRequest("GET", fmt.Sprintf("%s", path), nil)
	if err != nil {
		log.Errorf("Failed to create request for path %s: %v", path, err)
		return nil
	}
	if len(m.bearerToken) > 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", m.bearerToken))
	}

	resp, err := m.client.Client.Do(req)
	if err == nil {
		if resp.StatusCode != http.StatusOK {
			log.Errorf("Failed to read node summary response from '%s'; %s: %v", path, resp.Status, err)
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			// var data stats.Summary
			var data KubeletStatsSummary
			if err = json.Unmarshal(b, &data); err == nil {
				return &data
			}
			log.Errorf("Failed to unmarshal node summary response from '%s'; %v", path, string(b))
		} else {
			log.Errorf("Failed to read node summary response from '%s'; %v", path, err)
		}
	} else {
		log.Errorf("Failed to read node summary from '%s': %v", path, err)
	}
	return nil
}

func (m *Provider) listNodes() []*apiv1.Node {
	nodes, err := m.client.CoreV1().ListNodes(context.Background())
	if err == nil {
		return nodes.Items
	}
	log.Error(err)
	return []*apiv1.Node{}
}
<commit_msg>add safe-divide for possibly missing metric values<commit_after>package metrics

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/units"
	"github.com/ericchiang/k8s"
	apiv1 "github.com/ericchiang/k8s/api/v1"
	"github.com/matt-deboer/kuill/pkg/auth"
	"github.com/matt-deboer/kuill/pkg/helpers"
	log "github.com/sirupsen/logrus"
)

const serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"

// Provider gathers metrics from the underlying kubernetes cluster
type Provider struct {
	summary     *Summaries
	mutex       sync.RWMutex
	client      *k8s.Client
	killSwitch  chan struct{}
	bearerToken string
}

// NewMetricsProvider returns a Provider capable of returning metrics for the cluster
func NewMetricsProvider(kubeconfig string) (*Provider, error) {

	client, bearerToken, err := helpers.NewKubeClient(kubeconfig)
	if err != nil {
		return nil, err
	}

	m := &Provider{
		client:      client,
		killSwitch:  make(chan struct{}, 1),
		bearerToken: string(bearerToken),
	}

	m.summary = m.summarize()

	go func() {
		for {
			select {
			case _ = <-m.killSwitch:
				return
			default:
				time.Sleep(15 * time.Second)

				summary := m.summarize()

				m.mutex.Lock()
				m.summary = summary
				m.mutex.Unlock()
			}
		}
	}()

	return m, nil
}

// Close shuts down the metrics provider's background worker(s)
func (m *Provider) Close() {
	m.killSwitch <- struct{}{}
}

// GetMetrics returns a summarized metrics bundle for the entire cluster
func (m *Provider) GetMetrics(w http.ResponseWriter, r *http.Request, context auth.Context) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	data, err := json.Marshal(m.summary)
	if err != nil {
		log.Error(err)
		http.Error(w, "Failed to marshall metrics", http.StatusInternalServerError)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.Write(data)
	}
}

func (m *Provider) summarize() *Summaries {

	summary := &Summaries{
		Namespace: make(map[string]*Summary),
		Node:      make(map[string]*Summary),
		Pod:       make(map[string]*Summary),
	}
	aggregates := make(map[string]uint64)
	namespaces := make(map[string]bool)

	for _, node := range m.listNodes() {
		nodeName := *node.Metadata.Name
		nodeSummary := m.readNodeSummary(fmt.Sprintf("%s/api/v1/proxy/nodes/%s:10255/stats/summary", m.client.Endpoint, nodeName))
		if nodeSummary != nil {

			convertedSummary, convertedByNs := convertSummary(nodeSummary, node, summary)
			// aggregate at the cluster level
			aggregates["Cluster:totalMillicores"] += convertedSummary.CPU.Total
			aggregates["Cluster:memTotalBytes"] += convertedSummary.Memory.Total
			aggregates["Cluster:usageNanoCores"] += nodeSummary.Node.CPU.UsageNanoCores
			aggregates["Cluster:memUsageBytes"] += nodeSummary.Node.Memory.UsageBytes
			aggregates["Cluster:networkTxBytes"] += nodeSummary.Node.Network.TxBytes
			aggregates["Cluster:networkRxBytes"] += nodeSummary.Node.Network.RxBytes
			aggregates["Cluster:networkSeconds"] += uint64(nodeSummary.Node.Network.Time.Sub(nodeSummary.Node.StartTime).Seconds())
			aggregates["Cluster:fsCapacityBytes"] += nodeSummary.Node.Fs.CapacityBytes
			aggregates["Cluster:fsUsedBytes"] += nodeSummary.Node.Fs.UsedBytes

			// aggregate at the namespace level
			for ns, nsStat := range convertedByNs {
				aggregates["Namespace:"+ns+":fsUsedBytes"] += nsStat.Disk.Usage
				aggregates["Namespace:"+ns+":fsCapacityBytes"] += nsStat.Disk.Total
			}

			for _, podStats := range nodeSummary.Pods {
				ns := podStats.PodRef.Namespace
				aggregates["Namespace:"+ns+":pods"]++
				aggregates["Cluster:pods"]++
				convertedSummary.Pods.Usage++
				namespaces[ns] = true
				for _, volStats := range podStats.VolumeStats {
					aggregates["Cluster:volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Cluster:volUsedBytes"] += volStats.UsedBytes
					aggregates["Namespace:"+ns+":volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Namespace:"+ns+":volUsedBytes"] += volStats.UsedBytes
				}
				for _, c := range podStats.Containers {
					aggregates["Namespace:"+ns+":containers"]++
					aggregates["Cluster:containers"]++
					convertedSummary.Containers.Usage++
					if c.CPU != nil {
						aggregates["Namespace:"+ns+":usageCoreNanoSeconds"] += c.CPU.UsageCoreNanoSeconds
						aggregates["Namespace:"+ns+":usageNanoCores"] += c.CPU.UsageNanoCores
					}
					if c.Memory != nil {
						aggregates["Namespace:"+ns+":memAvailableBytes"] += c.Memory.AvailableBytes
						aggregates["Namespace:"+ns+":memUsageBytes"] += c.Memory.UsageBytes
						aggregates["Namespace:"+ns+":memRSSBytes"] += c.Memory.RSSBytes
						aggregates["Namespace:"+ns+":memWorkingSetBytes"] += c.Memory.WorkingSetBytes
					}
				}
				if podStats.Network != nil {
					aggregates["Namespace:"+ns+":networkTxBytes"] += podStats.Network.TxBytes
					aggregates["Namespace:"+ns+":networkRxBytes"] += podStats.Network.RxBytes
					aggregates["Namespace:"+ns+":networkSeconds"] += uint64(podStats.Network.Time.Unix() - podStats.StartTime.Unix())
				}
			}
			summary.Node[*node.Metadata.Name] = convertedSummary
		}
	}

	summary.Cluster = makeSummary("Cluster", aggregates)
	for ns := range namespaces {
		prefix := "Namespace:" + ns
		nsSummary := makeSummary(prefix, aggregates)

		// To be replaced by resource quota for the NS, should one exist
		nsSummary.CPU.Total = summary.Cluster.CPU.Total
		nsSummary.CPU.Ratio = safeDivideFloat(float64(nsSummary.CPU.Usage), float64(nsSummary.CPU.Total))
		nsSummary.Memory.Total = summary.Cluster.Memory.Total
		nsSummary.Memory.Ratio = safeDivideFloat(float64(nsSummary.Memory.Usage), float64(nsSummary.Memory.Total))

		summary.Namespace[ns] = nsSummary
	}

	return summary
}

func safeGet(ref *uint64) uint64 {
	if ref != nil {
		return *ref
	}
	return 0
}

func safeDivideInt(dividend, divisor uint64) uint64 {
	if divisor == 0 {
		return 0
	}
	return dividend / divisor
}

func safeDivideFloat(dividend, divisor float64) float64 {
	if divisor == 0.0 {
		return 0.0
	}
	return dividend / divisor
}

func convertSummary(summary *KubeletStatsSummary, node *apiv1.Node, summaries *Summaries) (*Summary, map[string]*Summary) {

	summaryByNs := make(map[string]*Summary)
	networkSeconds := uint64(summary.Node.Network.Time.Unix() - summary.Node.StartTime.Unix())
	volCapacityBytes := uint64(0)
	volUsageBytes := uint64(0)
	for _, podStats := range summary.Pods {

		podSummary := convertPodSummary(podStats)
		podKey := fmt.Sprintf("%s/%s", podStats.PodRef.Namespace, podStats.PodRef.Name)
		summaries.Pod[podKey] = podSummary
		nsSummary, ok := summaryByNs[podStats.PodRef.Namespace]
		if !ok {
			nsSummary = &Summary{Disk: &SummaryStat{Units: "bytes"}}
			summaryByNs[podStats.PodRef.Namespace] = nsSummary
		}
		nsSummary.Disk.Usage += podSummary.Disk.Usage
		nsSummary.Disk.Total += podSummary.Disk.Total

		volUsageBytes += podSummary.Volumes.Usage
		volCapacityBytes += podSummary.Volumes.Total
	}

	totalCPUCores, err := strconv.Atoi(*node.Status.Allocatable["cpu"].String_)
	if err != nil {
		log.Errorf("Failed to parse allocatable.cpu; %v", err)
	}
	allocatableBytesString := *node.Status.Allocatable["memory"].String_
	if strings.HasSuffix(allocatableBytesString, "i") {
		allocatableBytesString += "B"
	}
	totalMemoryBytes, err := units.ParseStrictBytes(allocatableBytesString)
	if err != nil {
		log.Errorf("Failed to parse allocatable.memory; %v", err)
	}

	if summary.Node.CPU == nil {
		summary.Node.CPU = &CPUStats{}
	}

	if summary.Node.Memory == nil {
		summary.Node.Memory = &MemoryStats{}
	}

	if summary.Node.Fs == nil {
		summary.Node.Fs = &FsStats{}
	}

	return &Summary{
		CPU: newSummaryStat(
			summary.Node.CPU.UsageNanoCores/1000000,
			uint64(totalCPUCores*1000),
			"millicores"),
		Memory: newSummaryStat(
			summary.Node.Memory.UsageBytes,
			uint64(totalMemoryBytes),
			"bytes"),
		Disk: newSummaryStat(
			summary.Node.Fs.UsedBytes,
			summary.Node.Fs.CapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsageBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			safeDivideInt(summary.Node.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(summary.Node.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 0,
		},
		Containers: &SummaryStat{
			Usage: 0,
		},
	}, summaryByNs
}

func convertPodSummary(pod PodStats) *Summary {

	networkSeconds := uint64(pod.Network.Time.Unix() - pod.StartTime.Unix())
	summary := &Summary{
		CPU:     &SummaryStat{Units: "millicores"},
		Memory:  &SummaryStat{Units: "bytes"},
		Volumes: &SummaryStat{Units: "bytes"},
		Disk:    &SummaryStat{Units: "bytes"},
		NetRx: newSummaryStat(
			safeDivideInt(pod.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(pod.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 1,
		},
		Containers: &SummaryStat{
			Usage: uint64(len(pod.Containers)),
		},
	}

	for _, volStats := range pod.VolumeStats {
		summary.Volumes.Usage += volStats.CapacityBytes
		summary.Volumes.Total += volStats.UsedBytes
	}

	for _, container := range pod.Containers {
		if container.CPU != nil {
			summary.CPU.Usage += container.CPU.UsageNanoCores / 1000000
		}
		if container.Memory != nil {
			summary.Memory.Usage += container.Memory.UsageBytes
		}
		if container.Rootfs != nil {
			summary.Disk.Usage += container.Rootfs.UsedBytes
		}
	}
	return summary
}

func makeSummary(prefix string, aggregates map[string]uint64) *Summary {

	usageNanoCores := aggregates[prefix+":usageNanoCores"]
	totalMillicores := aggregates[prefix+":totalMillicores"]
	memUsageBytes := aggregates[prefix+":memUsageBytes"]
	memTotalBytes := aggregates[prefix+":memTotalBytes"]
	networkTxBytes := aggregates[prefix+":networkTxBytes"]
	networkRxBytes := aggregates[prefix+":networkRxBytes"]
	// We create a fake timespan here with the current time as the endpoint;
	// the important part is that the duration is correct
	networkSeconds := aggregates[prefix+":networkSeconds"]
	if networkSeconds == 0 {
		networkSeconds = math.MaxUint64
	}

	fsCapacityBytes := aggregates[prefix+":fsCapacityBytes"]
	fsUsedBytes := aggregates[prefix+":fsUsedBytes"]
	volCapacityBytes := aggregates[prefix+":volCapacityBytes"]
	volUsedBytes := aggregates[prefix+":volUsedBytes"]
	podCount := aggregates[prefix+":pods"]
	containerCount := aggregates[prefix+":containers"]

	return &Summary{

		CPU: newSummaryStat(
			usageNanoCores/1000000,
			totalMillicores,
			"millicores"),
		Memory: newSummaryStat(
			memUsageBytes,
			memTotalBytes,
			"bytes"),
		Disk: newSummaryStat(
			fsUsedBytes,
			fsCapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsedBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			safeDivideInt(networkRxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(networkTxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: podCount,
		},
		Containers: &SummaryStat{
			Usage: containerCount,
		},
	}
}

func (m *Provider) readNodeSummary(path string) *KubeletStatsSummary {

	req, err := http.NewRequest("GET", fmt.Sprintf("%s", path), nil)
	if err != nil {
		log.Errorf("Failed to create request for path %s: %v", path, err)
		return nil
	}
	if len(m.bearerToken) > 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", m.bearerToken))
	}

	resp, err := m.client.Client.Do(req)
	if err == nil {
		if resp.StatusCode != http.StatusOK {
			log.Errorf("Failed to read node summary response from '%s'; %s: %v", path, resp.Status, err)
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			// var data stats.Summary
			var data KubeletStatsSummary
			if err = json.Unmarshal(b, &data); err == nil {
				return &data
			}
			log.Errorf("Failed to unmarshal node summary response from '%s'; %v", path, string(b))
		} else {
			log.Errorf("Failed to read node summary response from '%s'; %v", path, err)
		}
	} else {
		log.Errorf("Failed to read node summary from '%s': %v", path, err)
	}
	return nil
}

func (m *Provider) listNodes() []*apiv1.Node {
	nodes, err := m.client.CoreV1().ListNodes(context.Background())
	if err == nil {
		return nodes.Items
	}
	log.Error(err)
	return []*apiv1.Node{}
}
<|endoftext|>


<commit_before>// Copyright 2016 The prometheus-operator Authors
//
// 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 operator

import (
	"fmt"
	"strings"

	"github.com/coreos/prometheus-operator/pkg/spec"
	"k8s.io/client-go/1.5/pkg/api/v1"
	"k8s.io/client-go/1.5/pkg/apis/apps/v1alpha1"
	"k8s.io/client-go/1.5/pkg/util/intstr"
)

func makePetSet(p *spec.Prometheus, old *v1alpha1.PetSet, alertmanagers []string) *v1alpha1.PetSet {
	// TODO(fabxc): is this the right point to inject defaults?
	// Ideally we would do it before storing but that's currently not possible.
	// Potentially an update handler on first insertion.

	baseImage := p.Spec.BaseImage
	if baseImage == "" {
		baseImage = "quay.io/prometheus/prometheus"
	}
	version := p.Spec.Version
	if version == "" {
		version = "v1.3.0"
	}
	replicas := p.Spec.Replicas
	if replicas < 1 {
		replicas = 1
	}
	retention := p.Spec.Retention
	if retention == "" {
		retention = "24h"
	}
	image := fmt.Sprintf("%s:%s", baseImage, version)

	petset := &v1alpha1.PetSet{
		ObjectMeta: v1.ObjectMeta{
			Name: p.Name,
		},
		Spec: makePetSetSpec(p.Name, image, version, retention, replicas, alertmanagers),
	}
	if vc := p.Spec.Storage; vc == nil {
		petset.Spec.Template.Spec.Volumes = append(petset.Spec.Template.Spec.Volumes, v1.Volume{
			Name: fmt.Sprintf("%s-db", p.Name),
			VolumeSource: v1.VolumeSource{
				EmptyDir: &v1.EmptyDirVolumeSource{},
			},
		})
	} else {
		pvc := v1.PersistentVolumeClaim{
			ObjectMeta: v1.ObjectMeta{
				Name: fmt.Sprintf("%s-db", p.Name),
			},
			Spec: v1.PersistentVolumeClaimSpec{
				AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
				Resources:   vc.Resources,
				Selector:    vc.Selector,
			},
		}
		if len(vc.Class) > 0 {
			pvc.ObjectMeta.Annotations = map[string]string{
				"volume.beta.kubernetes.io/storage-class": vc.Class,
			}
		}
		petset.Spec.VolumeClaimTemplates = append(petset.Spec.VolumeClaimTemplates, pvc)
	}

	if old != nil {
		petset.Annotations = old.Annotations
	}
	return petset
}

func makeEmptyConfig(name string) *v1.ConfigMap {
	return &v1.ConfigMap{
		ObjectMeta: v1.ObjectMeta{
			Name: fmt.Sprintf("%s", name),
		},
		Data: map[string]string{
			"prometheus.yaml": "",
		},
	}
}

func makeEmptyRules(name string) *v1.ConfigMap {
	return &v1.ConfigMap{
		ObjectMeta: v1.ObjectMeta{
			Name: fmt.Sprintf("%s-rules", name),
		},
	}
}

func makePetSetService(p *spec.Prometheus) *v1.Service {
	svc := &v1.Service{
		ObjectMeta: v1.ObjectMeta{
			Name: "prometheus",
		},
		Spec: v1.ServiceSpec{
			ClusterIP: "None",
			Ports: []v1.ServicePort{
				{
					Name:       "web",
					Port:       9090,
					TargetPort: intstr.FromString("web"),
				},
			},
			Selector: map[string]string{
				"app": "prometheus",
			},
		},
	}
	return svc
}

func makePetSetSpec(name, image, version, retention string, replicas int32, alertmanagers []string) v1alpha1.PetSetSpec {
	// Prometheus may take quite long to shut down to checkpoint existing data.
	// Allow up to 10 minutes for clean termination.
	terminationGracePeriod := int64(600)

	return v1alpha1.PetSetSpec{
		ServiceName: "prometheus",
		Replicas:    &replicas,
		Template: v1.PodTemplateSpec{
			ObjectMeta: v1.ObjectMeta{
				Labels: map[string]string{
					"app":        "prometheus",
					"prometheus": name,
				},
				Annotations: map[string]string{
					"pod.alpha.kubernetes.io/initialized": "true",
				},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "prometheus",
						Image: image,
						Ports: []v1.ContainerPort{
							{
								Name:          "web",
								ContainerPort: 9090,
								Protocol:      v1.ProtocolTCP,
							},
						},
						Args: []string{
							"-storage.local.retention=" + retention,
							"-storage.local.memory-chunks=500000",
							"-storage.local.path=/var/prometheus/data",
							"-config.file=/etc/prometheus/config/prometheus.yaml",
							"-alertmanager.url=" + strings.Join(alertmanagers, ","),
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/config",
							},
							{
								Name:      "rules",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/rules",
							},
							{
								Name:      fmt.Sprintf("%s-db", name),
								MountPath: "/var/prometheus/data",
								SubPath: "prometheus",
							},
						},
						ReadinessProbe: &v1.Probe{
							Handler: v1.Handler{
								HTTPGet: &v1.HTTPGetAction{
									Path: "/status",
									Port: intstr.FromString("web"),
								},
							},
							InitialDelaySeconds: 1,
							TimeoutSeconds:      3,
							PeriodSeconds:       5,
							// For larger servers, restoring a checkpoint on startup may take quite a bit of time.
							// Wait up to 5 minutes.
							FailureThreshold: 100,
						},
					}, {
						Name:  "config-reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/config",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/config",
							},
						},
					}, {
						Name:  "rules-reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/rules/",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "rules",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/rules",
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &terminationGracePeriod,
				Volumes: []v1.Volume{
					{
						Name: "config",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: name,
								},
							},
						},
					},
					{
						Name: "rules",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: fmt.Sprintf("%s-rules", name),
								},
							},
						},
					},
				},
			},
		},
	}
}
<commit_msg>changed volumeMount subpath from 'prometheus' to 'prometheus-db'<commit_after>// Copyright 2016 The prometheus-operator Authors
//
// 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 operator

import (
	"fmt"
	"strings"

	"github.com/coreos/prometheus-operator/pkg/spec"
	"k8s.io/client-go/1.5/pkg/api/v1"
	"k8s.io/client-go/1.5/pkg/apis/apps/v1alpha1"
	"k8s.io/client-go/1.5/pkg/util/intstr"
)

func makePetSet(p *spec.Prometheus, old *v1alpha1.PetSet, alertmanagers []string) *v1alpha1.PetSet {
	// TODO(fabxc): is this the right point to inject defaults?
	// Ideally we would do it before storing but that's currently not possible.
	// Potentially an update handler on first insertion.

	baseImage := p.Spec.BaseImage
	if baseImage == "" {
		baseImage = "quay.io/prometheus/prometheus"
	}
	version := p.Spec.Version
	if version == "" {
		version = "v1.3.0"
	}
	replicas := p.Spec.Replicas
	if replicas < 1 {
		replicas = 1
	}
	retention := p.Spec.Retention
	if retention == "" {
		retention = "24h"
	}
	image := fmt.Sprintf("%s:%s", baseImage, version)

	petset := &v1alpha1.PetSet{
		ObjectMeta: v1.ObjectMeta{
			Name: p.Name,
		},
		Spec: makePetSetSpec(p.Name, image, version, retention, replicas, alertmanagers),
	}
	if vc := p.Spec.Storage; vc == nil {
		petset.Spec.Template.Spec.Volumes = append(petset.Spec.Template.Spec.Volumes, v1.Volume{
			Name: fmt.Sprintf("%s-db", p.Name),
			VolumeSource: v1.VolumeSource{
				EmptyDir: &v1.EmptyDirVolumeSource{},
			},
		})
	} else {
		pvc := v1.PersistentVolumeClaim{
			ObjectMeta: v1.ObjectMeta{
				Name: fmt.Sprintf("%s-db", p.Name),
			},
			Spec: v1.PersistentVolumeClaimSpec{
				AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
				Resources:   vc.Resources,
				Selector:    vc.Selector,
			},
		}
		if len(vc.Class) > 0 {
			pvc.ObjectMeta.Annotations = map[string]string{
				"volume.beta.kubernetes.io/storage-class": vc.Class,
			}
		}
		petset.Spec.VolumeClaimTemplates = append(petset.Spec.VolumeClaimTemplates, pvc)
	}

	if old != nil {
		petset.Annotations = old.Annotations
	}
	return petset
}

func makeEmptyConfig(name string) *v1.ConfigMap {
	return &v1.ConfigMap{
		ObjectMeta: v1.ObjectMeta{
			Name: fmt.Sprintf("%s", name),
		},
		Data: map[string]string{
			"prometheus.yaml": "",
		},
	}
}

func makeEmptyRules(name string) *v1.ConfigMap {
	return &v1.ConfigMap{
		ObjectMeta: v1.ObjectMeta{
			Name: fmt.Sprintf("%s-rules", name),
		},
	}
}

func makePetSetService(p *spec.Prometheus) *v1.Service {
	svc := &v1.Service{
		ObjectMeta: v1.ObjectMeta{
			Name: "prometheus",
		},
		Spec: v1.ServiceSpec{
			ClusterIP: "None",
			Ports: []v1.ServicePort{
				{
					Name:       "web",
					Port:       9090,
					TargetPort: intstr.FromString("web"),
				},
			},
			Selector: map[string]string{
				"app": "prometheus",
			},
		},
	}
	return svc
}

func makePetSetSpec(name, image, version, retention string, replicas int32, alertmanagers []string) v1alpha1.PetSetSpec {
	// Prometheus may take quite long to shut down to checkpoint existing data.
	// Allow up to 10 minutes for clean termination.
	terminationGracePeriod := int64(600)

	return v1alpha1.PetSetSpec{
		ServiceName: "prometheus",
		Replicas:    &replicas,
		Template: v1.PodTemplateSpec{
			ObjectMeta: v1.ObjectMeta{
				Labels: map[string]string{
					"app":        "prometheus",
					"prometheus": name,
				},
				Annotations: map[string]string{
					"pod.alpha.kubernetes.io/initialized": "true",
				},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "prometheus",
						Image: image,
						Ports: []v1.ContainerPort{
							{
								Name:          "web",
								ContainerPort: 9090,
								Protocol:      v1.ProtocolTCP,
							},
						},
						Args: []string{
							"-storage.local.retention=" + retention,
							"-storage.local.memory-chunks=500000",
							"-storage.local.path=/var/prometheus/data",
							"-config.file=/etc/prometheus/config/prometheus.yaml",
							"-alertmanager.url=" + strings.Join(alertmanagers, ","),
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/config",
							},
							{
								Name:      "rules",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/rules",
							},
							{
								Name:      fmt.Sprintf("%s-db", name),
								MountPath: "/var/prometheus/data",
								SubPath:   "prometheus-db",
							},
						},
						ReadinessProbe: &v1.Probe{
							Handler: v1.Handler{
								HTTPGet: &v1.HTTPGetAction{
									Path: "/status",
									Port: intstr.FromString("web"),
								},
							},
							InitialDelaySeconds: 1,
							TimeoutSeconds:      3,
							PeriodSeconds:       5,
							// For larger servers, restoring a checkpoint on startup may take quite a bit of time.
							// Wait up to 5 minutes.
							FailureThreshold: 100,
						},
					}, {
						Name:  "config-reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/config",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/config",
							},
						},
					}, {
						Name:  "rules-reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/rules/",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "rules",
								ReadOnly:  true,
								MountPath: "/etc/prometheus/rules",
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &terminationGracePeriod,
				Volumes: []v1.Volume{
					{
						Name: "config",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: name,
								},
							},
						},
					},
					{
						Name: "rules",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: fmt.Sprintf("%s-rules", name),
								},
							},
						},
					},
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gempir/gempbot/pkg/emotechief"
	"github.com/gempir/gempbot/pkg/eventsub"
	"github.com/nicklaw5/helix/v2"
)

func (a *Api) EventSubHandler(w http.ResponseWriter, r *http.Request) {
	emoteChief := emotechief.NewEmoteChief(a.cfg, a.db, a.helixClient, a.bot.ChatClient)
	eventSubManager := eventsub.NewEventSubManager(a.cfg, a.helixClient, a.db, emoteChief, a.bot.ChatClient)

	event, err := eventSubManager.HandleWebhook(w, r)
	if err != nil || len(event) == 0 {
		if err != nil {
			http.Error(w, err.Error(), err.Status())
		}
		return
	}

	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPointsCustomRewardRedemptionAdd {
		eventSubManager.HandleChannelPointsCustomRewardRedemption(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPointsCustomRewardRedemptionUpdate {
		eventSubManager.HandleChannelPointsCustomRewardRedemption(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionBegin {
		eventSubManager.HandlePredictionBegin(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionLock {
		eventSubManager.HandlePredictionLock(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionEnd {
		eventSubManager.HandlePredictionEnd(event)
		return
	}

	http.Error(w, "Invalid event type", http.StatusBadRequest)
}
<commit_msg>eventsub<commit_after>package server

import (
	"net/http"

	"github.com/gempir/gempbot/pkg/emotechief"
	"github.com/gempir/gempbot/pkg/eventsub"
	"github.com/gempir/gempbot/pkg/log"
	"github.com/nicklaw5/helix/v2"
)

func (a *Api) EventSubHandler(w http.ResponseWriter, r *http.Request) {
	log.Infof("New event sub request %s %s", r.Method, r.URL.Path)
	emoteChief := emotechief.NewEmoteChief(a.cfg, a.db, a.helixClient, a.bot.ChatClient)
	eventSubManager := eventsub.NewEventSubManager(a.cfg, a.helixClient, a.db, emoteChief, a.bot.ChatClient)

	event, err := eventSubManager.HandleWebhook(w, r)
	if err != nil || len(event) == 0 {
		if err != nil {
			http.Error(w, err.Error(), err.Status())
		}
		return
	}

	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPointsCustomRewardRedemptionAdd {
		eventSubManager.HandleChannelPointsCustomRewardRedemption(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPointsCustomRewardRedemptionUpdate {
		eventSubManager.HandleChannelPointsCustomRewardRedemption(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionBegin {
		eventSubManager.HandlePredictionBegin(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionLock {
		eventSubManager.HandlePredictionLock(event)
		return
	}
	if r.URL.Query().Get("type") == helix.EventSubTypeChannelPredictionEnd {
		eventSubManager.HandlePredictionEnd(event)
		return
	}

	http.Error(w, "Invalid event type", http.StatusBadRequest)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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 kube

import (
	"k8s.io/client-go/tools/clientcmd"
)

// GetConfig returns a Kubernetes client config for a given context.
func GetConfig(context string, kubeconfig string) clientcmd.ClientConfig {
	rules := clientcmd.NewDefaultClientConfigLoadingRules()
	rules.DefaultClientConfig = &clientcmd.DefaultClientConfig

	overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}

	if context != "" {
		overrides.CurrentContext = context
	}

	if kubeconfig != "" {
		rules.ExplicitPath = kubeconfig
	}

	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
}
<commit_msg>Load all client auth plugins in the cli (#1695)<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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 kube

import (
	_ "k8s.io/client-go/plugin/pkg/client/auth" // Load all client auth plugins for gcp, azure, etc
	"k8s.io/client-go/tools/clientcmd"
)

// GetConfig returns a Kubernetes client config for a given context.
func GetConfig(context string, kubeconfig string) clientcmd.ClientConfig {
	rules := clientcmd.NewDefaultClientConfigLoadingRules()
	rules.DefaultClientConfig = &clientcmd.DefaultClientConfig

	overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}

	if context != "" {
		overrides.CurrentContext = context
	}

	if kubeconfig != "" {
		rules.ExplicitPath = kubeconfig
	}

	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
}
<|endoftext|>


<commit_before>// +build linux

/*
 * Minio Cloud Storage, (C) 2016,2017 Minio, Inc.
 *
 * 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 sys

import (
	"os"
	"syscall"

	"github.com/minio/minio/pkg/cgroup"
)

// Get the final system memory limit chosen by the user.
// by default without any configuration on a vanilla Linux
// system you would see physical RAM limit. If cgroup
// is configured at some point in time this function
// would return the memory limit chosen for the given pid.
func getMemoryLimit() (sysLimit uint64, err error) {
	if sysLimit, err = getSysinfoMemoryLimit(); err != nil {
		// Physical memory info is not accessible, just exit here.
		return 0, err
	}

	// Following code is deliberately ignoring the error.
	cGroupLimit, gerr := cgroup.GetMemoryLimit(os.Getpid())
	if gerr != nil {
		// Upon error just return system limit.
		return sysLimit, nil
	}

	// cgroup limit is lesser than system limit means
	// user wants to limit the memory usage further
	// treat cgroup limit as the system limit.
	if cGroupLimit <= sysLimit {
		sysLimit = cGroupLimit
	}

	// Final system limit.
	return sysLimit, nil

}

// Get physical RAM size of the node.
func getSysinfoMemoryLimit() (limit uint64, err error) {
	var si syscall.Sysinfo_t
	if err = syscall.Sysinfo(&si); err != nil {
		return 0, err
	}

	// Total RAM is always the multiplicative value
	// of unit size and total ram.
	limit = uint64(si.Unit) * si.Totalram
	return limit, nil
}

// GetStats - return system statistics, currently only
// supported value is TotalRAM.
func GetStats() (stats Stats, err error) {
	var limit uint64
	limit, err = getMemoryLimit()
	if err != nil {
		return Stats{}, err
	}

	stats.TotalRAM = limit
	return stats, nil
}
<commit_msg>build: Fix compilation in 32 bits platforms (#4052)<commit_after>// +build linux

/*
 * Minio Cloud Storage, (C) 2016,2017 Minio, Inc.
 *
 * 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 sys

import (
	"os"
	"syscall"

	"github.com/minio/minio/pkg/cgroup"
)

// Get the final system memory limit chosen by the user.
// by default without any configuration on a vanilla Linux
// system you would see physical RAM limit. If cgroup
// is configured at some point in time this function
// would return the memory limit chosen for the given pid.
func getMemoryLimit() (sysLimit uint64, err error) {
	if sysLimit, err = getSysinfoMemoryLimit(); err != nil {
		// Physical memory info is not accessible, just exit here.
		return 0, err
	}

	// Following code is deliberately ignoring the error.
	cGroupLimit, gerr := cgroup.GetMemoryLimit(os.Getpid())
	if gerr != nil {
		// Upon error just return system limit.
		return sysLimit, nil
	}

	// cgroup limit is lesser than system limit means
	// user wants to limit the memory usage further
	// treat cgroup limit as the system limit.
	if cGroupLimit <= sysLimit {
		sysLimit = cGroupLimit
	}

	// Final system limit.
	return sysLimit, nil

}

// Get physical RAM size of the node.
func getSysinfoMemoryLimit() (limit uint64, err error) {
	var si syscall.Sysinfo_t
	if err = syscall.Sysinfo(&si); err != nil {
		return 0, err
	}

	// Some fields in syscall.Sysinfo_t have different  integer sizes
	// in different platform architectures. Cast all fields to uint64.
	totalRAM := uint64(si.Totalram)
	unit := uint64(si.Unit)

	// Total RAM is always the multiplicative value
	// of unit size and total ram.
	limit = unit * totalRAM
	return limit, nil
}

// GetStats - return system statistics, currently only
// supported value is TotalRAM.
func GetStats() (stats Stats, err error) {
	var limit uint64
	limit, err = getMemoryLimit()
	if err != nil {
		return Stats{}, err
	}

	stats.TotalRAM = limit
	return stats, nil
}
<|endoftext|>


<commit_before>package qingcloud

import "time"

func stringSliceDiff(nl, ol []string) ([]string, []string) {
	var additions []string
	var deletions []string
	for i := 0; i < 2; i++ {
		for _, n := range nl {
			found := false
			for _, o := range ol {
				if n == o {
					found = true
					break
				}
			}
			if !found {
				if i == 0 {
					additions = append(additions, n)
				} else {
					deletions = append(deletions, n)
				}
			}
		}
		if i == 0 {
			nl, ol = ol, nl
		}
	}
	return additions, deletions
}
func IsServerBusy(RetCode int) bool {
	if RetCode == 5100 {
		return true
	}
	return false
}

func retry(attempts int, sleep time.Duration, fn func() error) error {
	if err := fn(); err != nil {
		if attempts--; attempts > 0 {
			time.Sleep(sleep)
			return retry(attempts, 2*sleep, fn)
		}
		return err
	}
	return nil
}

func retrySimple(fn func() error) error {
	return retry(100, 10*time.Second, fn)
}
<commit_msg>retry help<commit_after>package qingcloud

import (
	"time"
	"math/rand"
)

func stringSliceDiff(nl, ol []string) ([]string, []string) {
	var additions []string
	var deletions []string
	for i := 0; i < 2; i++ {
		for _, n := range nl {
			found := false
			for _, o := range ol {
				if n == o {
					found = true
					break
				}
			}
			if !found {
				if i == 0 {
					additions = append(additions, n)
				} else {
					deletions = append(deletions, n)
				}
			}
		}
		if i == 0 {
			nl, ol = ol, nl
		}
	}
	return additions, deletions
}
func IsServerBusy(RetCode int) bool {
	if RetCode == 5100 {
		return true
	}
	return false
}

func retry(attempts int, sleep time.Duration, fn func() error) error {
	if err := fn(); err != nil {
		if s, ok := err.(stop); ok {
			// Return the original error for later checking
			return s.error
		}

		if attempts--; attempts > 0 {
			// Add some randomness to prevent creating a Thundering Herd
			jitter := time.Duration(rand.Int63n(int64(sleep)))
			sleep = sleep + jitter/2

			time.Sleep(sleep)
			return retry(attempts, 2*sleep, fn)
		}
		return err
	}

	return nil
}
type stop struct {
	error
}
func retrySimple(fn func() error) error {
	return retry(100, 10*time.Second, fn)
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/go-kit/kit/log"
	"gopkg.in/yaml.v2"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
	"github.com/weaveworks/flux/image"
	"github.com/weaveworks/flux/resource"
)

// ResourceScopes maps resource definitions (GroupVersionKind) to whether they are namespaced or not
type ResourceScopes map[schema.GroupVersionKind]v1beta1.ResourceScope

// namespacer assigns namespaces to manifests that need it (or "" if
// the manifest should not have a namespace.
type namespacer interface {
	// EffectiveNamespace gives the namespace that would be used were
	// the manifest to be applied. This may be "", indicating that it
	// should not have a namespace (i.e., it's a cluster-level
	// resource).
	EffectiveNamespace(manifest kresource.KubeManifest, knownScopes ResourceScopes) (string, error)
}

// manifests is an implementation of cluster.Manifests, particular to
// Kubernetes. Aside from loading manifests from files, it does some
// "post-processsing" to make sure the view of the manifests is what
// would be applied; in particular, it fills in the namespace of
// manifests that would be given a default namespace when applied.
type manifests struct {
	namespacer       namespacer
	logger           log.Logger
	resourceWarnings map[string]struct{}
}

func NewManifests(ns namespacer, logger log.Logger) *manifests {
	return &manifests{
		namespacer:       ns,
		logger:           logger,
		resourceWarnings: map[string]struct{}{},
	}
}

func getCRDScopes(manifests map[string]kresource.KubeManifest) ResourceScopes {
	result := ResourceScopes{}
	for _, km := range manifests {
		if km.GetKind() == "CustomResourceDefinition" {
			var crd v1beta1.CustomResourceDefinition
			if err := yaml.Unmarshal(km.Bytes(), &crd); err != nil {
				// The CRD can't be parsed, so we (intentionally) ignore it and
				// just hope for EffectiveNamespace() to find its scope in the cluster if needed.
				continue
			}
			crdVersions := crd.Spec.Versions
			if len(crdVersions) == 0 {
				crdVersions = []v1beta1.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}}
			}
			for _, crdVersion := range crdVersions {
				gvk := schema.GroupVersionKind{
					Group:   crd.Spec.Group,
					Version: crdVersion.Name,
					Kind:    crd.Spec.Names.Kind,
				}
				result[gvk] = crd.Spec.Scope
			}
		}
	}
	return result
}

func (m *manifests) setEffectiveNamespaces(manifests map[string]kresource.KubeManifest) (map[string]resource.Resource, error) {
	knownScopes := getCRDScopes(manifests)
	result := map[string]resource.Resource{}
	for _, km := range manifests {
		resID := km.ResourceID()
		resIDStr := resID.String()
		ns, err := m.namespacer.EffectiveNamespace(km, knownScopes)
		if err != nil {
			if strings.Contains(err.Error(), "not found") {
				// discard the resource and keep going after making sure we logged about it
				if _, warningLogged := m.resourceWarnings[resIDStr]; !warningLogged {
					_, kind, name := resID.Components()
					partialResIDStr := kind + "/" + name
					m.logger.Log(
						"warn", fmt.Sprintf("cannot find scope of resource %s: %s", partialResIDStr, err),
						"impact", fmt.Sprintf("resource %s will be excluded until its scope is available", partialResIDStr))
					m.resourceWarnings[resIDStr] = struct{}{}
				}
				continue
			}
			return nil, err
		}
		km.SetNamespace(ns)
		if _, warningLogged := m.resourceWarnings[resIDStr]; warningLogged {
			// indicate that we found the resource's scope and allow logging a warning again
			m.logger.Log("info", fmt.Sprintf("found scope of resource %s, back in bussiness!", km.ResourceID().String()))
			delete(m.resourceWarnings, resIDStr)
		}
		result[km.ResourceID().String()] = km
	}
	return result, nil
}

func (m *manifests) LoadManifests(baseDir string, paths []string) (map[string]resource.Resource, error) {
	manifests, err := kresource.Load(baseDir, paths)
	if err != nil {
		return nil, err
	}
	return m.setEffectiveNamespaces(manifests)
}

func (m *manifests) ParseManifest(def []byte, source string) (map[string]resource.Resource, error) {
	resources, err := kresource.ParseMultidoc(def, source)
	if err != nil {
		return nil, err
	}
	// Note: setEffectiveNamespaces() won't work for CRD instances whose CRD is yet to be created
	// (due to the CRD not being present in kresources).
	// We could get out of our way to fix this (or give a better error) but:
	// 1. With the exception of HelmReleases CRD instances are not workloads anyways.
	// 2. The problem is eventually fixed by the first successful sync.
	result, err := m.setEffectiveNamespaces(resources)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (m *manifests) SetWorkloadContainerImage(def []byte, id resource.ID, container string, image image.Ref) ([]byte, error) {
	return updateWorkload(def, id, container, image)
}

func (m *manifests) CreateManifestPatch(originalManifests, modifiedManifests []byte, originalSource, modifiedSource string) ([]byte, error) {
	return createManifestPatch(originalManifests, modifiedManifests, originalSource, modifiedSource)
}

func (m *manifests) ApplyManifestPatch(originalManifests, patchManifests []byte, originalSource, patchSource string) ([]byte, error) {
	return applyManifestPatch(originalManifests, patchManifests, originalSource, patchSource)
}

func (m *manifests) AppendManifestToBuffer(manifest []byte, buffer *bytes.Buffer) error {
	return appendYAMLToBuffer(manifest, buffer)
}

func appendYAMLToBuffer(manifest []byte, buffer *bytes.Buffer) error {
	separator := "---\n"
	bytes := buffer.Bytes()
	if len(bytes) > 0 && bytes[len(bytes)-1] != '\n' {
		separator = "\n---\n"
	}
	if _, err := buffer.WriteString(separator); err != nil {
		return fmt.Errorf("cannot write to internal buffer: %s", err)
	}
	if _, err := buffer.Write(manifest); err != nil {
		return fmt.Errorf("cannot write to internal buffer: %s", err)
	}
	return nil
}

// UpdateWorkloadPolicies in policies.go
<commit_msg>Correct bussiness to business (#2295)<commit_after>package kubernetes

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/go-kit/kit/log"
	"gopkg.in/yaml.v2"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
	"github.com/weaveworks/flux/image"
	"github.com/weaveworks/flux/resource"
)

// ResourceScopes maps resource definitions (GroupVersionKind) to whether they are namespaced or not
type ResourceScopes map[schema.GroupVersionKind]v1beta1.ResourceScope

// namespacer assigns namespaces to manifests that need it (or "" if
// the manifest should not have a namespace.
type namespacer interface {
	// EffectiveNamespace gives the namespace that would be used were
	// the manifest to be applied. This may be "", indicating that it
	// should not have a namespace (i.e., it's a cluster-level
	// resource).
	EffectiveNamespace(manifest kresource.KubeManifest, knownScopes ResourceScopes) (string, error)
}

// manifests is an implementation of cluster.Manifests, particular to
// Kubernetes. Aside from loading manifests from files, it does some
// "post-processsing" to make sure the view of the manifests is what
// would be applied; in particular, it fills in the namespace of
// manifests that would be given a default namespace when applied.
type manifests struct {
	namespacer       namespacer
	logger           log.Logger
	resourceWarnings map[string]struct{}
}

func NewManifests(ns namespacer, logger log.Logger) *manifests {
	return &manifests{
		namespacer:       ns,
		logger:           logger,
		resourceWarnings: map[string]struct{}{},
	}
}

func getCRDScopes(manifests map[string]kresource.KubeManifest) ResourceScopes {
	result := ResourceScopes{}
	for _, km := range manifests {
		if km.GetKind() == "CustomResourceDefinition" {
			var crd v1beta1.CustomResourceDefinition
			if err := yaml.Unmarshal(km.Bytes(), &crd); err != nil {
				// The CRD can't be parsed, so we (intentionally) ignore it and
				// just hope for EffectiveNamespace() to find its scope in the cluster if needed.
				continue
			}
			crdVersions := crd.Spec.Versions
			if len(crdVersions) == 0 {
				crdVersions = []v1beta1.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}}
			}
			for _, crdVersion := range crdVersions {
				gvk := schema.GroupVersionKind{
					Group:   crd.Spec.Group,
					Version: crdVersion.Name,
					Kind:    crd.Spec.Names.Kind,
				}
				result[gvk] = crd.Spec.Scope
			}
		}
	}
	return result
}

func (m *manifests) setEffectiveNamespaces(manifests map[string]kresource.KubeManifest) (map[string]resource.Resource, error) {
	knownScopes := getCRDScopes(manifests)
	result := map[string]resource.Resource{}
	for _, km := range manifests {
		resID := km.ResourceID()
		resIDStr := resID.String()
		ns, err := m.namespacer.EffectiveNamespace(km, knownScopes)
		if err != nil {
			if strings.Contains(err.Error(), "not found") {
				// discard the resource and keep going after making sure we logged about it
				if _, warningLogged := m.resourceWarnings[resIDStr]; !warningLogged {
					_, kind, name := resID.Components()
					partialResIDStr := kind + "/" + name
					m.logger.Log(
						"warn", fmt.Sprintf("cannot find scope of resource %s: %s", partialResIDStr, err),
						"impact", fmt.Sprintf("resource %s will be excluded until its scope is available", partialResIDStr))
					m.resourceWarnings[resIDStr] = struct{}{}
				}
				continue
			}
			return nil, err
		}
		km.SetNamespace(ns)
		if _, warningLogged := m.resourceWarnings[resIDStr]; warningLogged {
			// indicate that we found the resource's scope and allow logging a warning again
			m.logger.Log("info", fmt.Sprintf("found scope of resource %s, back in business!", km.ResourceID().String()))
			delete(m.resourceWarnings, resIDStr)
		}
		result[km.ResourceID().String()] = km
	}
	return result, nil
}

func (m *manifests) LoadManifests(baseDir string, paths []string) (map[string]resource.Resource, error) {
	manifests, err := kresource.Load(baseDir, paths)
	if err != nil {
		return nil, err
	}
	return m.setEffectiveNamespaces(manifests)
}

func (m *manifests) ParseManifest(def []byte, source string) (map[string]resource.Resource, error) {
	resources, err := kresource.ParseMultidoc(def, source)
	if err != nil {
		return nil, err
	}
	// Note: setEffectiveNamespaces() won't work for CRD instances whose CRD is yet to be created
	// (due to the CRD not being present in kresources).
	// We could get out of our way to fix this (or give a better error) but:
	// 1. With the exception of HelmReleases CRD instances are not workloads anyways.
	// 2. The problem is eventually fixed by the first successful sync.
	result, err := m.setEffectiveNamespaces(resources)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (m *manifests) SetWorkloadContainerImage(def []byte, id resource.ID, container string, image image.Ref) ([]byte, error) {
	return updateWorkload(def, id, container, image)
}

func (m *manifests) CreateManifestPatch(originalManifests, modifiedManifests []byte, originalSource, modifiedSource string) ([]byte, error) {
	return createManifestPatch(originalManifests, modifiedManifests, originalSource, modifiedSource)
}

func (m *manifests) ApplyManifestPatch(originalManifests, patchManifests []byte, originalSource, patchSource string) ([]byte, error) {
	return applyManifestPatch(originalManifests, patchManifests, originalSource, patchSource)
}

func (m *manifests) AppendManifestToBuffer(manifest []byte, buffer *bytes.Buffer) error {
	return appendYAMLToBuffer(manifest, buffer)
}

func appendYAMLToBuffer(manifest []byte, buffer *bytes.Buffer) error {
	separator := "---\n"
	bytes := buffer.Bytes()
	if len(bytes) > 0 && bytes[len(bytes)-1] != '\n' {
		separator = "\n---\n"
	}
	if _, err := buffer.WriteString(separator); err != nil {
		return fmt.Errorf("cannot write to internal buffer: %s", err)
	}
	if _, err := buffer.Write(manifest); err != nil {
		return fmt.Errorf("cannot write to internal buffer: %s", err)
	}
	return nil
}

// UpdateWorkloadPolicies in policies.go
<|endoftext|>