<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// 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 ident

import (
	"github.com/m3db/m3x/checked"
	"github.com/m3db/m3x/context"
	"github.com/m3db/m3x/pool"
)

const (
	defaultCapacityOptions    = 16
	defaultMaxCapacityOptions = 32
)

// PoolOptions is a set of pooling options.
type PoolOptions struct {
	IDPoolOptions           pool.ObjectPoolOptions
	TagsPoolOptions         pool.ObjectPoolOptions
	TagsCapacity            int
	TagsMaxCapacity         int
	TagsIteratorPoolOptions pool.ObjectPoolOptions
}

func (o PoolOptions) defaultsIfNotSet() PoolOptions {
	if o.IDPoolOptions == nil {
		o.IDPoolOptions = pool.NewObjectPoolOptions()
	}
	if o.TagsPoolOptions == nil {
		o.TagsPoolOptions = pool.NewObjectPoolOptions()
	}
	if o.TagsCapacity == 0 {
		o.TagsCapacity = defaultCapacityOptions
	}
	if o.TagsMaxCapacity == 0 {
		o.TagsMaxCapacity = defaultMaxCapacityOptions
	}
	if o.TagsIteratorPoolOptions == nil {
		o.TagsIteratorPoolOptions = pool.NewObjectPoolOptions()
	}
	return o
}

// NewPool constructs a new simple Pool.
func NewPool(
	bytesPool pool.CheckedBytesPool,
	opts PoolOptions,
) Pool {
	opts = opts.defaultsIfNotSet()

	p := &simplePool{
		bytesPool: bytesPool,
		pool:      pool.NewObjectPool(opts.IDPoolOptions),
		tagArrayPool: newTagArrayPool(tagArrayPoolOpts{
			Options:     opts.TagsPoolOptions,
			Capacity:    opts.TagsCapacity,
			MaxCapacity: opts.TagsMaxCapacity,
		}),
		itersPool: pool.NewObjectPool(opts.TagsIteratorPoolOptions),
	}
	p.pool.Init(func() interface{} {
		return &id{pool: p}
	})
	p.tagArrayPool.Init()
	p.itersPool.Init(func() interface{} {
		return newTagSliceIter(Tags{}, p)
	})

	return p
}

type simplePool struct {
	bytesPool    pool.CheckedBytesPool
	pool         pool.ObjectPool
	tagArrayPool tagArrayPool
	itersPool    pool.ObjectPool
}

func (p *simplePool) GetBinaryID(ctx context.Context, v checked.Bytes) ID {
	id := p.BinaryID(v)
	ctx.RegisterFinalizer(id)
	return id
}

func (p *simplePool) BinaryID(v checked.Bytes) ID {
	id := p.pool.Get().(*id)
	v.IncRef()
	id.pool, id.data = p, v
	return id
}

func (p *simplePool) GetBinaryTag(
	ctx context.Context,
	name checked.Bytes,
	value checked.Bytes,
) Tag {
	return Tag{
		Name:  TagName(p.GetBinaryID(ctx, name)),
		Value: TagValue(p.GetBinaryID(ctx, value)),
	}
}

func (p *simplePool) BinaryTag(
	name checked.Bytes,
	value checked.Bytes,
) Tag {
	return Tag{
		Name:  TagName(p.BinaryID(name)),
		Value: TagValue(p.BinaryID(value)),
	}
}

func (p *simplePool) GetStringID(ctx context.Context, v string) ID {
	id := p.StringID(v)
	ctx.RegisterFinalizer(id)
	return id
}

func (p *simplePool) StringID(v string) ID {
	data := p.bytesPool.Get(len(v))
	data.IncRef()
	data.AppendAll([]byte(v))
	data.DecRef()

	return p.BinaryID(data)
}

func (p *simplePool) GetTagsIterator(c context.Context) TagsIterator {
	iter := p.itersPool.Get().(*tagSliceIter)
	c.RegisterCloser(iter)
	return iter
}

func (p *simplePool) TagsIterator() TagsIterator {
	return p.itersPool.Get().(*tagSliceIter)
}

func (p *simplePool) Tags() Tags {
	return Tags{
		values: p.tagArrayPool.Get(),
		pool:   p,
	}
}

func (p *simplePool) Put(v ID) {
	p.pool.Put(v)
}

func (p *simplePool) PutTag(t Tag) {
	p.Put(t.Name)
	p.Put(t.Value)
}

func (p *simplePool) PutTags(t Tags) {
	p.tagArrayPool.Put(t.values)
}

func (p *simplePool) PutTagsIterator(iter TagsIterator) {
	iter.Reset(Tags{})
	p.itersPool.Put(iter)
}

func (p *simplePool) GetStringTag(ctx context.Context, name string, value string) Tag {
	return Tag{
		Name:  TagName(p.GetStringID(ctx, name)),
		Value: TagValue(p.GetStringID(ctx, value)),
	}
}

func (p *simplePool) StringTag(name string, value string) Tag {
	return Tag{
		Name:  TagName(p.StringID(name)),
		Value: TagValue(p.StringID(value)),
	}
}

func (p *simplePool) Clone(existing ID) ID {
	id := p.pool.Get().(*id)

	// NB(rartoul): Do not modify this function without careful
	// benchmarking on a hot production workload. When we tried to
	// introduce a helper function for the lines below we saw no
	// discrepancy in micro-benchmarks, but heavy perf degradation in production.
	data := existing.Bytes()
	newData := p.bytesPool.Get(len(data))
	newData.IncRef()
	newData.AppendAll(data)

	id.pool, id.data = p, newData

	return id
}

func (p *simplePool) CloneTag(t Tag) Tag {
	return Tag{
		Name:  p.Clone(t.Name),
		Value: p.Clone(t.Value),
	}
}

func (p *simplePool) CloneTags(t Tags) Tags {
	tags := p.tagArrayPool.Get()[:0]
	for _, tag := range t.Values() {
		tags = append(tags, p.CloneTag(tag))
	}
	return Tags{
		values: tags,
		pool:   p,
	}
}
<commit_msg>remove invalid comment (#210)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// 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 ident

import (
	"github.com/m3db/m3x/checked"
	"github.com/m3db/m3x/context"
	"github.com/m3db/m3x/pool"
)

const (
	defaultCapacityOptions    = 16
	defaultMaxCapacityOptions = 32
)

// PoolOptions is a set of pooling options.
type PoolOptions struct {
	IDPoolOptions           pool.ObjectPoolOptions
	TagsPoolOptions         pool.ObjectPoolOptions
	TagsCapacity            int
	TagsMaxCapacity         int
	TagsIteratorPoolOptions pool.ObjectPoolOptions
}

func (o PoolOptions) defaultsIfNotSet() PoolOptions {
	if o.IDPoolOptions == nil {
		o.IDPoolOptions = pool.NewObjectPoolOptions()
	}
	if o.TagsPoolOptions == nil {
		o.TagsPoolOptions = pool.NewObjectPoolOptions()
	}
	if o.TagsCapacity == 0 {
		o.TagsCapacity = defaultCapacityOptions
	}
	if o.TagsMaxCapacity == 0 {
		o.TagsMaxCapacity = defaultMaxCapacityOptions
	}
	if o.TagsIteratorPoolOptions == nil {
		o.TagsIteratorPoolOptions = pool.NewObjectPoolOptions()
	}
	return o
}

// NewPool constructs a new simple Pool.
func NewPool(
	bytesPool pool.CheckedBytesPool,
	opts PoolOptions,
) Pool {
	opts = opts.defaultsIfNotSet()

	p := &simplePool{
		bytesPool: bytesPool,
		pool:      pool.NewObjectPool(opts.IDPoolOptions),
		tagArrayPool: newTagArrayPool(tagArrayPoolOpts{
			Options:     opts.TagsPoolOptions,
			Capacity:    opts.TagsCapacity,
			MaxCapacity: opts.TagsMaxCapacity,
		}),
		itersPool: pool.NewObjectPool(opts.TagsIteratorPoolOptions),
	}
	p.pool.Init(func() interface{} {
		return &id{pool: p}
	})
	p.tagArrayPool.Init()
	p.itersPool.Init(func() interface{} {
		return newTagSliceIter(Tags{}, p)
	})

	return p
}

type simplePool struct {
	bytesPool    pool.CheckedBytesPool
	pool         pool.ObjectPool
	tagArrayPool tagArrayPool
	itersPool    pool.ObjectPool
}

func (p *simplePool) GetBinaryID(ctx context.Context, v checked.Bytes) ID {
	id := p.BinaryID(v)
	ctx.RegisterFinalizer(id)
	return id
}

func (p *simplePool) BinaryID(v checked.Bytes) ID {
	id := p.pool.Get().(*id)
	v.IncRef()
	id.pool, id.data = p, v
	return id
}

func (p *simplePool) GetBinaryTag(
	ctx context.Context,
	name checked.Bytes,
	value checked.Bytes,
) Tag {
	return Tag{
		Name:  TagName(p.GetBinaryID(ctx, name)),
		Value: TagValue(p.GetBinaryID(ctx, value)),
	}
}

func (p *simplePool) BinaryTag(
	name checked.Bytes,
	value checked.Bytes,
) Tag {
	return Tag{
		Name:  TagName(p.BinaryID(name)),
		Value: TagValue(p.BinaryID(value)),
	}
}

func (p *simplePool) GetStringID(ctx context.Context, v string) ID {
	id := p.StringID(v)
	ctx.RegisterFinalizer(id)
	return id
}

func (p *simplePool) StringID(v string) ID {
	data := p.bytesPool.Get(len(v))
	data.IncRef()
	data.AppendAll([]byte(v))
	data.DecRef()

	return p.BinaryID(data)
}

func (p *simplePool) GetTagsIterator(c context.Context) TagsIterator {
	iter := p.itersPool.Get().(*tagSliceIter)
	c.RegisterCloser(iter)
	return iter
}

func (p *simplePool) TagsIterator() TagsIterator {
	return p.itersPool.Get().(*tagSliceIter)
}

func (p *simplePool) Tags() Tags {
	return Tags{
		values: p.tagArrayPool.Get(),
		pool:   p,
	}
}

func (p *simplePool) Put(v ID) {
	p.pool.Put(v)
}

func (p *simplePool) PutTag(t Tag) {
	p.Put(t.Name)
	p.Put(t.Value)
}

func (p *simplePool) PutTags(t Tags) {
	p.tagArrayPool.Put(t.values)
}

func (p *simplePool) PutTagsIterator(iter TagsIterator) {
	iter.Reset(Tags{})
	p.itersPool.Put(iter)
}

func (p *simplePool) GetStringTag(ctx context.Context, name string, value string) Tag {
	return Tag{
		Name:  TagName(p.GetStringID(ctx, name)),
		Value: TagValue(p.GetStringID(ctx, value)),
	}
}

func (p *simplePool) StringTag(name string, value string) Tag {
	return Tag{
		Name:  TagName(p.StringID(name)),
		Value: TagValue(p.StringID(value)),
	}
}

func (p *simplePool) Clone(existing ID) ID {
	var (
		id      = p.pool.Get().(*id)
		data    = existing.Bytes()
		newData = p.bytesPool.Get(len(data))
	)

	newData.IncRef()
	newData.AppendAll(data)

	id.pool, id.data = p, newData

	return id
}

func (p *simplePool) CloneTag(t Tag) Tag {
	return Tag{
		Name:  p.Clone(t.Name),
		Value: p.Clone(t.Value),
	}
}

func (p *simplePool) CloneTags(t Tags) Tags {
	tags := p.tagArrayPool.Get()[:0]
	for _, tag := range t.Values() {
		tags = append(tags, p.CloneTag(tag))
	}
	return Tags{
		values: tags,
		pool:   p,
	}
}
<|endoftext|>


<commit_before>package raftgorums

import (
	"container/list"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/go-kit/kit/metrics"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
	pb "github.com/relab/raft/raftgorums/raftpb"
)

// RequestVote implements gorums.RaftServer.
func (r *Raft) RequestVote(ctx context.Context, req *pb.RequestVoteRequest) (*pb.RequestVoteResponse, error) {
	return r.HandleRequestVoteRequest(req), nil
}

// AppendEntries implements gorums.RaftServer.
func (r *Raft) AppendEntries(ctx context.Context, req *pb.AppendEntriesRequest) (*pb.AppendEntriesResponse, error) {
	return r.HandleAppendEntriesRequest(req), nil
}

// InstallSnapshot implements gorums.RaftServer.
func (r *Raft) InstallSnapshot(ctx context.Context, snapshot *commonpb.Snapshot) (*pb.InstallSnapshotResponse, error) {
	return r.HandleInstallSnapshotRequest(snapshot), nil
}

// CatchMeUp implements gorums.RaftServer.
func (r *Raft) CatchMeUp(ctx context.Context, req *pb.CatchMeUpRequest) (res *pb.Empty, err error) {
	res = &pb.Empty{}
	r.match[r.mem.getNodeID(req.FollowerID)] <- req.NextIndex
	return
}

// HandleRequestVoteRequest must be called when receiving a RequestVoteRequest,
// the return value must be delivered to the requester.
func (r *Raft) HandleRequestVoteRequest(req *pb.RequestVoteRequest) *pb.RequestVoteResponse {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.rvreq)
		defer timer.ObserveDuration()
	}

	var voteGranted bool
	defer func() {
		r.logger.WithFields(logrus.Fields{
			"currentterm": r.currentTerm,
			"requestterm": req.Term,
			"prevote":     req.PreVote,
			"candidateid": req.CandidateID,
			"votegranted": voteGranted,
		}).Infoln("Got vote request")
	}()

	// #RV1 Reply false if term < currentTerm.
	if req.Term < r.currentTerm {
		return &pb.RequestVoteResponse{Term: r.currentTerm}
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	if req.Term > r.currentTerm && !req.PreVote {
		r.becomeFollower(req.Term)
	}

	voted := r.votedFor != None

	if req.PreVote && (r.heardFromLeader || (voted && req.Term == r.currentTerm)) {
		// We don't grant pre-votes if we have recently heard from a
		// leader or already voted in the pre-term.
		return &pb.RequestVoteResponse{Term: r.currentTerm}
	}

	lastIndex := r.storage.NextIndex() - 1
	lastLogTerm := r.logTerm(lastIndex)

	// We can grant a vote in the same term, as long as it's to the same
	// candidate. This is useful if the response was lost, and the candidate
	// sends another request.
	alreadyVotedForCandidate := r.votedFor == req.CandidateID

	// If the logs have last entries with different terms, the log with the
	// later term is more up-to-date.
	laterTerm := req.LastLogTerm > lastLogTerm

	// If the logs end with the same term, whichever log is longer is more
	// up-to-date.
	longEnough := req.LastLogTerm == lastLogTerm && req.LastLogIndex >= lastIndex

	// We can only grant a vote if: we have not voted yet, we vote for the
	// same candidate again, or this is a pre-vote.
	canGrantVote := !voted || alreadyVotedForCandidate || req.PreVote

	// #RV2 If votedFor is null or candidateId, and candidate's log is at
	// least as up-to-date as receiver's log, grant vote.
	voteGranted = canGrantVote && (laterTerm || longEnough)

	if voteGranted {
		if req.PreVote {
			return &pb.RequestVoteResponse{VoteGranted: true, Term: req.Term}
		}

		r.votedFor = req.CandidateID
		r.storage.Set(raft.KeyVotedFor, req.CandidateID)

		// #F2 If election timeout elapses without receiving
		// AppendEntries RPC from current leader or granting a vote to
		// candidate: convert to candidate. Here we are granting a vote
		// to a candidate so we reset the election timeout.
		r.resetElection = true
		r.resetBaseline = true

		return &pb.RequestVoteResponse{VoteGranted: true, Term: r.currentTerm}
	}

	// #RV2 The candidate's log was not up-to-date
	return &pb.RequestVoteResponse{Term: r.currentTerm}
}

// HandleAppendEntriesRequest must be called when receiving a
// AppendEntriesRequest, the return value must be delivered to the requester.
func (r *Raft) HandleAppendEntriesRequest(req *pb.AppendEntriesRequest) *pb.AppendEntriesResponse {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.aereq)
		defer timer.ObserveDuration()
	}

	reqLogger := r.logger.WithFields(logrus.Fields{
		"currentterm":  r.currentTerm,
		"requestterm":  req.Term,
		"leaderid":     req.LeaderID,
		"prevlogindex": req.PrevLogIndex,
		"prevlogterm":  req.PrevLogTerm,
		"commitindex":  req.CommitIndex,
		"lenentries":   len(req.Entries),
	})
	reqLogger.Infoln("Got AppendEntries")

	logLen := r.storage.NextIndex() - 1

	res := &pb.AppendEntriesResponse{
		Term:       r.currentTerm,
		MatchIndex: logLen,
	}

	// #AE1 Reply false if term < currentTerm.
	if req.Term < r.currentTerm {
		return res
	}

	prevTerm := r.logTerm(req.PrevLogIndex)

	// An AppendEntries request is always successful for the first index. A
	// leader can only be elected leader if its log matches that of a
	// majority and our log is guaranteed to be at least 0 in length.
	firstIndex := req.PrevLogIndex == 0

	// The index preceding the entries we are going to replicate must be in our log.
	gotPrevIndex := req.PrevLogIndex <= logLen
	// The term must match to satisfy the log matching property.
	sameTerm := req.PrevLogTerm == prevTerm

	// If the previous entry is in our log, then our log matches the leaders
	// up till and including the previous entry. And we can safely replicate
	// next new entries.
	gotPrevEntry := gotPrevIndex && sameTerm

	success := firstIndex || gotPrevEntry

	// #A2 If RPC request or response contains term T > currentTerm: set
	// currentTerm = T, convert to follower. Transition to follower upon
	// receiving an AppendEntries call.
	if req.Term > r.currentTerm || r.state != Follower {
		r.becomeFollower(req.Term)
		res.Term = req.Term
	}

	if r.metricsEnabled {
		rmetrics.leader.Set(float64(req.LeaderID))
	}

	// We acknowledge this server as the leader as it's has the highest term
	// we have seen, and there can only be one leader per term.
	r.leader = req.LeaderID
	r.heardFromLeader = true
	r.seenLeader = true

	if !success {
		r.cureqout <- &catchUpReq{
			leaderID: req.LeaderID,
			// TODO term: req.Term, ?
			matchIndex: res.MatchIndex,
		}

		return res
	}

	var toSave []*commonpb.Entry
	index := req.PrevLogIndex

	for _, entry := range req.Entries {
		// Increment first so we start at previous index + 1.
		index++

		// If the terms don't match, our logs conflict at this index. On
		// the first conflict this will truncate the log to the lowest
		// common matching index. After that it will fill the log with
		// the new entries from the leader. This is because entry.Term
		// will always conflict with term 0, which will be returned for
		// indexes outside our log.
		if entry.Term != r.logTerm(index) {
			logLen = r.storage.NextIndex() - 1
			for logLen > index-1 {
				// If we are overwriting the latest
				// configuration, rollback to the committed one.
				if logLen == r.mem.getIndex() {
					r.mem.rollback()
				}
				r.storage.RemoveEntries(logLen, logLen)
				logLen = r.storage.NextIndex() - 1
			}
			toSave = append(toSave, entry)
		}
	}

	if len(toSave) > 0 {
		r.storage.StoreEntries(toSave)
	}
	logLen = r.storage.NextIndex() - 1

	for _, entry := range toSave {
		if entry.EntryType == commonpb.EntryReconf {
			var reconf commonpb.ReconfRequest
			err := reconf.Unmarshal(entry.Data)

			if err != nil {
				panic("could not unmarshal reconf")
			}

			r.mem.setPending(&reconf)
			r.mem.set(entry.Index)
		}
	}

	old := r.commitIndex
	// Commit index can not exceed the length of our log.
	r.commitIndex = min(req.CommitIndex, logLen)

	if r.metricsEnabled {
		rmetrics.commitIndex.Set(float64(r.commitIndex))
	}

	if r.commitIndex > old {
		r.logger.WithFields(logrus.Fields{
			"oldcommitindex": old,
			"commitindex":    r.commitIndex,
		}).Infoln("Set commit index")

		r.newCommit(old)
	}

	reqLogger.WithFields(logrus.Fields{
		"lensaved": len(toSave),
		"lenlog":   logLen,
		"success":  success,
	}).Infoln("Saved entries to stable storage")

	res.Success = true
	return res
}

func (r *Raft) HandleInstallSnapshotRequest(snapshot *commonpb.Snapshot) (res *pb.InstallSnapshotResponse) {
	r.mu.Lock()
	defer r.mu.Unlock()

	res = &pb.InstallSnapshotResponse{
		Term: r.currentTerm,
	}

	return
}

// HandleRequestVoteResponse must be invoked when receiving a
// RequestVoteResponse.
func (r *Raft) HandleRequestVoteResponse(response *pb.RequestVoteResponse) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.rvres)
		defer timer.ObserveDuration()
	}

	r.logger.WithFields(logrus.Fields{
		"currentterm":  r.currentTerm,
		"responseterm": response.Term,
		"votegranted":  response.VoteGranted,
	}).Infoln("Got vote response")

	term := r.currentTerm

	if r.preElection {
		term++
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	if response.Term > term {
		r.becomeFollower(response.Term)

		return
	}

	// Ignore late response
	if response.Term < term {
		return
	}

	// Cont. from startElection(). We have now received a response from Gorums.

	// #C5 If votes received from majority of server: become leader.
	// Make sure we have not stepped down while waiting for replies.
	if r.state == Candidate && response.VoteGranted {
		if r.preElection {
			r.preElection = false
			select {
			case r.startElectionNow <- struct{}{}:
			case <-r.stop:
			}
			return
		}

		// We have received at least a quorum of votes.
		// We are the leader for this term. See Raft Paper Figure 2 -> Rules for Servers -> Leaders.

		if r.metricsEnabled {
			rmetrics.leader.Set(float64(r.id))
		}

		r.logger.WithFields(logrus.Fields{
			"currentterm": r.currentTerm,
		}).Infoln("Elected leader")

		logLen := r.storage.NextIndex() - 1

		r.state = Leader
		r.leader = r.id
		r.seenLeader = true
		r.heardFromLeader = true
		r.nextIndex = logLen + 1
		r.pending = list.New()
		r.pendingReads = nil
		r.mem.setStable(false)

		// Empty queue.
	EMPTYCH:
		for {
			select {
			case <-r.queue:
			default:
				// Paper §8: We add a no-op, so that the leader
				// commits an entry from its own term. This
				// ensures that the leader knows which entries
				// are committed.
				promise, _ := raft.NewPromiseEntry(&commonpb.Entry{
					EntryType: commonpb.EntryInternal,
					Term:      r.currentTerm,
					Data:      raft.NOOP,
				})
				r.queue <- promise
				break EMPTYCH
			}
		}

		// TODO r.sendAppendEntries()?

		return
	}

	r.preElection = true

	// #C7 If election timeout elapses: start new election.
	// This will happened if we don't receive enough replies in time. Or we lose the election but don't see a higher term number.
}

// HandleAppendEntriesResponse must be invoked when receiving an
// AppendEntriesResponse.
func (r *Raft) HandleAppendEntriesResponse(response *pb.AppendEntriesQFResponse, maxIndex uint64) {
	r.mu.Lock()
	defer func() {
		r.mu.Unlock()
		r.advanceCommitIndex()
	}()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.aeres)
		defer timer.ObserveDuration()
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	// If we didn't get a response from a majority (excluding self) step down.
	if response.Term > r.currentTerm || response.Replies < uint64((len(r.mem.get().NodeIDs())+1)/2) {
		// Become follower.
		select {
		case r.toggle <- struct{}{}:
			r.logger.Warnln("Leader stepping down")
		case <-r.stop:
		}

		return
	}

	// Heartbeat to a majority.
	r.resetElection = true

	// Ignore late response
	if response.Term < r.currentTerm {
		return
	}

	if response.Success {
		r.matchIndex = maxIndex
		r.nextIndex = r.matchIndex + 1
		r.logger.WithFields(logrus.Fields{
			"matchindex": r.matchIndex,
			"nextindex":  r.nextIndex,
		}).Warnln("Setting matchindex")

		return
	}

	// If AppendEntries was not successful lower match index.
	r.nextIndex = max(1, min(r.nextIndex-r.burst, r.matchIndex+1))
}

func (r *Raft) HandleInstallSnapshotResponse(res *pb.InstallSnapshotResponse) bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	if res.Term > r.currentTerm {
		r.becomeFollower(res.Term)

		return false
	}

	return true
}
<commit_msg>raftgorums/incoming.go: Fix election timeout during catch-up<commit_after>package raftgorums

import (
	"container/list"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/go-kit/kit/metrics"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
	pb "github.com/relab/raft/raftgorums/raftpb"
)

// RequestVote implements gorums.RaftServer.
func (r *Raft) RequestVote(ctx context.Context, req *pb.RequestVoteRequest) (*pb.RequestVoteResponse, error) {
	return r.HandleRequestVoteRequest(req), nil
}

// AppendEntries implements gorums.RaftServer.
func (r *Raft) AppendEntries(ctx context.Context, req *pb.AppendEntriesRequest) (*pb.AppendEntriesResponse, error) {
	return r.HandleAppendEntriesRequest(req), nil
}

// InstallSnapshot implements gorums.RaftServer.
func (r *Raft) InstallSnapshot(ctx context.Context, snapshot *commonpb.Snapshot) (*pb.InstallSnapshotResponse, error) {
	return r.HandleInstallSnapshotRequest(snapshot), nil
}

// CatchMeUp implements gorums.RaftServer.
func (r *Raft) CatchMeUp(ctx context.Context, req *pb.CatchMeUpRequest) (res *pb.Empty, err error) {
	res = &pb.Empty{}
	r.match[r.mem.getNodeID(req.FollowerID)] <- req.NextIndex
	return
}

// HandleRequestVoteRequest must be called when receiving a RequestVoteRequest,
// the return value must be delivered to the requester.
func (r *Raft) HandleRequestVoteRequest(req *pb.RequestVoteRequest) *pb.RequestVoteResponse {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.rvreq)
		defer timer.ObserveDuration()
	}

	var voteGranted bool
	defer func() {
		r.logger.WithFields(logrus.Fields{
			"currentterm": r.currentTerm,
			"requestterm": req.Term,
			"prevote":     req.PreVote,
			"candidateid": req.CandidateID,
			"votegranted": voteGranted,
		}).Infoln("Got vote request")
	}()

	// #RV1 Reply false if term < currentTerm.
	if req.Term < r.currentTerm {
		return &pb.RequestVoteResponse{Term: r.currentTerm}
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	if req.Term > r.currentTerm && !req.PreVote {
		r.becomeFollower(req.Term)
	}

	voted := r.votedFor != None

	if req.PreVote && (r.heardFromLeader || (voted && req.Term == r.currentTerm)) {
		// We don't grant pre-votes if we have recently heard from a
		// leader or already voted in the pre-term.
		return &pb.RequestVoteResponse{Term: r.currentTerm}
	}

	lastIndex := r.storage.NextIndex() - 1
	lastLogTerm := r.logTerm(lastIndex)

	// We can grant a vote in the same term, as long as it's to the same
	// candidate. This is useful if the response was lost, and the candidate
	// sends another request.
	alreadyVotedForCandidate := r.votedFor == req.CandidateID

	// If the logs have last entries with different terms, the log with the
	// later term is more up-to-date.
	laterTerm := req.LastLogTerm > lastLogTerm

	// If the logs end with the same term, whichever log is longer is more
	// up-to-date.
	longEnough := req.LastLogTerm == lastLogTerm && req.LastLogIndex >= lastIndex

	// We can only grant a vote if: we have not voted yet, we vote for the
	// same candidate again, or this is a pre-vote.
	canGrantVote := !voted || alreadyVotedForCandidate || req.PreVote

	// #RV2 If votedFor is null or candidateId, and candidate's log is at
	// least as up-to-date as receiver's log, grant vote.
	voteGranted = canGrantVote && (laterTerm || longEnough)

	if voteGranted {
		if req.PreVote {
			return &pb.RequestVoteResponse{VoteGranted: true, Term: req.Term}
		}

		r.votedFor = req.CandidateID
		r.storage.Set(raft.KeyVotedFor, req.CandidateID)

		// #F2 If election timeout elapses without receiving
		// AppendEntries RPC from current leader or granting a vote to
		// candidate: convert to candidate. Here we are granting a vote
		// to a candidate so we reset the election timeout.
		r.resetElection = true
		r.resetBaseline = true

		return &pb.RequestVoteResponse{VoteGranted: true, Term: r.currentTerm}
	}

	// #RV2 The candidate's log was not up-to-date
	return &pb.RequestVoteResponse{Term: r.currentTerm}
}

// HandleAppendEntriesRequest must be called when receiving a
// AppendEntriesRequest, the return value must be delivered to the requester.
func (r *Raft) HandleAppendEntriesRequest(req *pb.AppendEntriesRequest) *pb.AppendEntriesResponse {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.aereq)
		defer timer.ObserveDuration()
	}

	reqLogger := r.logger.WithFields(logrus.Fields{
		"currentterm":  r.currentTerm,
		"requestterm":  req.Term,
		"leaderid":     req.LeaderID,
		"prevlogindex": req.PrevLogIndex,
		"prevlogterm":  req.PrevLogTerm,
		"commitindex":  req.CommitIndex,
		"lenentries":   len(req.Entries),
	})
	reqLogger.Infoln("Got AppendEntries")

	logLen := r.storage.NextIndex() - 1

	res := &pb.AppendEntriesResponse{
		Term:       r.currentTerm,
		MatchIndex: logLen,
	}

	// #AE1 Reply false if term < currentTerm.
	if req.Term < r.currentTerm {
		return res
	}

	prevTerm := r.logTerm(req.PrevLogIndex)

	// An AppendEntries request is always successful for the first index. A
	// leader can only be elected leader if its log matches that of a
	// majority and our log is guaranteed to be at least 0 in length.
	firstIndex := req.PrevLogIndex == 0

	// The index preceding the entries we are going to replicate must be in our log.
	gotPrevIndex := req.PrevLogIndex <= logLen
	// The term must match to satisfy the log matching property.
	sameTerm := req.PrevLogTerm == prevTerm

	// If the previous entry is in our log, then our log matches the leaders
	// up till and including the previous entry. And we can safely replicate
	// next new entries.
	gotPrevEntry := gotPrevIndex && sameTerm

	success := firstIndex || gotPrevEntry

	// #A2 If RPC request or response contains term T > currentTerm: set
	// currentTerm = T, convert to follower. Transition to follower upon
	// receiving an AppendEntries call.
	if req.Term > r.currentTerm || r.state != Follower {
		r.becomeFollower(req.Term)
		res.Term = req.Term
	}

	if r.metricsEnabled {
		rmetrics.leader.Set(float64(req.LeaderID))
	}

	// We acknowledge this server as the leader as it's has the highest term
	// we have seen, and there can only be one leader per term.
	r.leader = req.LeaderID
	r.heardFromLeader = true
	r.seenLeader = true

	// Don't timeout during catch up.
	if uint64(len(req.Entries)) > r.burst {
		r.resetElection = true
	}

	if !success {
		r.cureqout <- &catchUpReq{
			leaderID: req.LeaderID,
			// TODO term: req.Term, ?
			matchIndex: res.MatchIndex,
		}

		return res
	}

	var toSave []*commonpb.Entry
	index := req.PrevLogIndex

	for _, entry := range req.Entries {
		// Increment first so we start at previous index + 1.
		index++

		// If the terms don't match, our logs conflict at this index. On
		// the first conflict this will truncate the log to the lowest
		// common matching index. After that it will fill the log with
		// the new entries from the leader. This is because entry.Term
		// will always conflict with term 0, which will be returned for
		// indexes outside our log.
		if entry.Term != r.logTerm(index) {
			logLen = r.storage.NextIndex() - 1
			for logLen > index-1 {
				// If we are overwriting the latest
				// configuration, rollback to the committed one.
				if logLen == r.mem.getIndex() {
					r.mem.rollback()
				}
				r.storage.RemoveEntries(logLen, logLen)
				logLen = r.storage.NextIndex() - 1
			}
			toSave = append(toSave, entry)
		}
	}

	if len(toSave) > 0 {
		r.storage.StoreEntries(toSave)
	}
	logLen = r.storage.NextIndex() - 1

	for _, entry := range toSave {
		if entry.EntryType == commonpb.EntryReconf {
			var reconf commonpb.ReconfRequest
			err := reconf.Unmarshal(entry.Data)

			if err != nil {
				panic("could not unmarshal reconf")
			}

			r.mem.setPending(&reconf)
			r.mem.set(entry.Index)
		}
	}

	old := r.commitIndex
	// Commit index can not exceed the length of our log.
	r.commitIndex = min(req.CommitIndex, logLen)

	if r.metricsEnabled {
		rmetrics.commitIndex.Set(float64(r.commitIndex))
	}

	if r.commitIndex > old {
		r.logger.WithFields(logrus.Fields{
			"oldcommitindex": old,
			"commitindex":    r.commitIndex,
		}).Infoln("Set commit index")

		r.newCommit(old)
	}

	reqLogger.WithFields(logrus.Fields{
		"lensaved": len(toSave),
		"lenlog":   logLen,
		"success":  success,
	}).Infoln("Saved entries to stable storage")

	res.Success = true
	return res
}

func (r *Raft) HandleInstallSnapshotRequest(snapshot *commonpb.Snapshot) (res *pb.InstallSnapshotResponse) {
	r.mu.Lock()
	defer r.mu.Unlock()

	res = &pb.InstallSnapshotResponse{
		Term: r.currentTerm,
	}

	return
}

// HandleRequestVoteResponse must be invoked when receiving a
// RequestVoteResponse.
func (r *Raft) HandleRequestVoteResponse(response *pb.RequestVoteResponse) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.rvres)
		defer timer.ObserveDuration()
	}

	r.logger.WithFields(logrus.Fields{
		"currentterm":  r.currentTerm,
		"responseterm": response.Term,
		"votegranted":  response.VoteGranted,
	}).Infoln("Got vote response")

	term := r.currentTerm

	if r.preElection {
		term++
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	if response.Term > term {
		r.becomeFollower(response.Term)

		return
	}

	// Ignore late response
	if response.Term < term {
		return
	}

	// Cont. from startElection(). We have now received a response from Gorums.

	// #C5 If votes received from majority of server: become leader.
	// Make sure we have not stepped down while waiting for replies.
	if r.state == Candidate && response.VoteGranted {
		if r.preElection {
			r.preElection = false
			select {
			case r.startElectionNow <- struct{}{}:
			case <-r.stop:
			}
			return
		}

		// We have received at least a quorum of votes.
		// We are the leader for this term. See Raft Paper Figure 2 -> Rules for Servers -> Leaders.

		if r.metricsEnabled {
			rmetrics.leader.Set(float64(r.id))
		}

		r.logger.WithFields(logrus.Fields{
			"currentterm": r.currentTerm,
		}).Infoln("Elected leader")

		logLen := r.storage.NextIndex() - 1

		r.state = Leader
		r.leader = r.id
		r.seenLeader = true
		r.heardFromLeader = true
		r.nextIndex = logLen + 1
		r.pending = list.New()
		r.pendingReads = nil
		r.mem.setStable(false)

		// Empty queue.
	EMPTYCH:
		for {
			select {
			case <-r.queue:
			default:
				// Paper §8: We add a no-op, so that the leader
				// commits an entry from its own term. This
				// ensures that the leader knows which entries
				// are committed.
				promise, _ := raft.NewPromiseEntry(&commonpb.Entry{
					EntryType: commonpb.EntryInternal,
					Term:      r.currentTerm,
					Data:      raft.NOOP,
				})
				r.queue <- promise
				break EMPTYCH
			}
		}

		// TODO r.sendAppendEntries()?

		return
	}

	r.preElection = true

	// #C7 If election timeout elapses: start new election.
	// This will happened if we don't receive enough replies in time. Or we lose the election but don't see a higher term number.
}

// HandleAppendEntriesResponse must be invoked when receiving an
// AppendEntriesResponse.
func (r *Raft) HandleAppendEntriesResponse(response *pb.AppendEntriesQFResponse, maxIndex uint64) {
	r.mu.Lock()
	defer func() {
		r.mu.Unlock()
		r.advanceCommitIndex()
	}()
	if r.metricsEnabled {
		timer := metrics.NewTimer(rmetrics.aeres)
		defer timer.ObserveDuration()
	}

	// #A2 If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower.
	// If we didn't get a response from a majority (excluding self) step down.
	if response.Term > r.currentTerm || response.Replies < uint64((len(r.mem.get().NodeIDs())+1)/2) {
		// Become follower.
		select {
		case r.toggle <- struct{}{}:
			r.logger.Warnln("Leader stepping down")
		case <-r.stop:
		}

		return
	}

	// Heartbeat to a majority.
	r.resetElection = true

	// Ignore late response
	if response.Term < r.currentTerm {
		return
	}

	if response.Success {
		r.matchIndex = maxIndex
		r.nextIndex = r.matchIndex + 1
		r.logger.WithFields(logrus.Fields{
			"matchindex": r.matchIndex,
			"nextindex":  r.nextIndex,
		}).Warnln("Setting matchindex")

		return
	}

	// If AppendEntries was not successful lower match index.
	r.nextIndex = max(1, min(r.nextIndex-r.burst, r.matchIndex+1))
}

func (r *Raft) HandleInstallSnapshotResponse(res *pb.InstallSnapshotResponse) bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	if res.Term > r.currentTerm {
		r.becomeFollower(res.Term)

		return false
	}

	return true
}
<|endoftext|>


<commit_before>package datamapper

import "time"

type BucketFileNamer interface {
	Name(time.Time) string
}

type RFC3339BucketFileNamer struct{}

func (_ RFC3339BucketFileNamer) Name(bucketTime time.Time) string {
	return bucketTime.Format(time.RFC3339)
}
<commit_msg>Force bucket filenames to be UTC timestamps<commit_after>package datamapper

import "time"

type BucketFileNamer interface {
	Name(time.Time) string
}

type RFC3339BucketFileNamer struct{}

func (_ RFC3339BucketFileNamer) Name(bucketTime time.Time) string {
	return bucketTime.In(time.UTC).Format(time.RFC3339)
}
<|endoftext|>


<commit_before>package atomas
import (
	"container/list"
)

const (
	PLUS_SIGN  int = iota
)

func EvaluateBoard(arrayBoard []int) (int, []int) {
	score := 0
	multiplier := 1
	board := toList(arrayBoard)
	score, multiplier, board = lookForPossibleCombinations(board, multiplier)
	return score * multiplier, toArray(board)
}

func lookForPossibleCombinations(board *list.List, multiplier int) (int, int, *list.List) {
	score := 0
	for e := board.Front(); e != nil; e = e.Next() {
		if e.Value == PLUS_SIGN && shouldMergeElements(board, e) {
			score, multiplier, board = combineElements(board, e, multiplier)
		}
	}
	return score, multiplier, board
}

func combineElements(board *list.List, element *list.Element, multiplier int) (int, int, *list.List) {
	score := 0
	score += nextWithLoop(board, element).Value.(int) * 2
	element.Value = Max(nextWithLoop(board, element).Value.(int), element.Value.(int)) + 1
	board, newAccElement := removeNeighbours(board, element)
	if (shouldMergeElements(board, newAccElement)) {
		partialScore := 0
		partialScore, multiplier, board = combineElements(board, newAccElement, multiplier + 1)
		score += partialScore
	} else if (aNewMergeEmerged(board)) {
		partialScore := 0
		partialScore, multiplier, board = lookForPossibleCombinations(board, multiplier + 1)
		score += partialScore
	}
	return score, multiplier, board
}

func shouldMergeElements(board *list.List, element *list.Element) bool {
	return (board.Len() > 2 && isSurroundingSame(board, element) && theyAreNotPluses(board, element))
}

func aNewMergeEmerged(board *list.List) bool {
	for e := board.Front(); e != nil; e = e.Next() {
		if e.Value == PLUS_SIGN && shouldMergeElements(board, e) {
			return true
		}
	}
	return false
}

func theyAreNotPluses(board *list.List, element *list.Element) bool {
	return nextWithLoop(board, element).Value != PLUS_SIGN
}

func removeNeighbours(board *list.List, element *list.Element) (*list.List, *list.Element) {
	newBoard := list.New()
	var newAccElement *list.Element = nil
	for e := board.Front(); e != nil; e = e.Next() {
		if (e != prevWithLoop(board, element) && e != nextWithLoop(board, element)) {
			if (e == element) {
				newAccElement = newBoard.PushBack(e.Value.(int))
			}else {
				newBoard.PushBack(e.Value.(int))
			}
		}
	}
	return newBoard, newAccElement
}

func nextWithLoop(board *list.List, element *list.Element) *list.Element {
	if (element.Next() != nil ) {
		return element.Next()
	}else {
		return board.Front()
	}
}

func prevWithLoop(board *list.List, element *list.Element) *list.Element {
	if (element.Prev() != nil ) {
		return element.Prev()
	}else {
		return board.Back()
	}
}

func isSurroundingSame(board *list.List, element *list.Element) bool {
	return nextWithLoop(board, element).Value == prevWithLoop(board, element).Value
}

func toList(board []int) *list.List {
	result := list.New()
	for _, element := range board {
		result.PushBack(int(element))
	}
	return result
}

func toArray(board *list.List) []int {
	array := make([]int, board.Len())
	i := 0
	for e := board.Front(); e != nil; e = e.Next() {
		array[i] = e.Value.(int)
		i += 1
	}
	return array
}
<commit_msg>simplify removeNeighbours by removing it<commit_after>package atomas
import (
	"container/list"
)

const (
	PLUS_SIGN  int = iota
)

func EvaluateBoard(arrayBoard []int) (int, []int) {
	score := 0
	multiplier := 1
	board := toList(arrayBoard)
	score, multiplier, board = lookForPossibleCombinations(board, multiplier)
	return score * multiplier, toArray(board)
}

func lookForPossibleCombinations(board *list.List, multiplier int) (int, int, *list.List) {
	score := 0
	for e := board.Front(); e != nil; e = e.Next() {
		if e.Value == PLUS_SIGN && shouldMergeElements(board, e) {
			score, multiplier, board = combineElements(board, e, multiplier)
		}
	}
	return score, multiplier, board
}

func combineElements(board *list.List, element *list.Element, multiplier int) (int, int, *list.List) {
	next := nextWithLoop(board, element)
	prev := prevWithLoop(board, element)
	surroundingValue := next.Value.(int)
	score := surroundingValue * 2
	element.Value = Max(surroundingValue, element.Value.(int)) + 1
	board.Remove(prev)
	board.Remove(next)
	if (shouldMergeElements(board, element)) {
		partialScore, multiplier, board := combineElements(board, element, multiplier + 1)
		return partialScore + score, multiplier, board
	} else if (aNewMergeEmerged(board)) {
		partialScore, multiplier, board := lookForPossibleCombinations(board, multiplier + 1)
		return partialScore + score, multiplier, board
	}else{
		return score, multiplier, board
	}
}

func shouldMergeElements(board *list.List, element *list.Element) bool {
	return (board.Len() > 2 && isSurroundingSame(board, element) && theyAreNotPluses(board, element))
}

func aNewMergeEmerged(board *list.List) bool {
	for e := board.Front(); e != nil; e = e.Next() {
		if e.Value == PLUS_SIGN && shouldMergeElements(board, e) {
			return true
		}
	}
	return false
}

func theyAreNotPluses(board *list.List, element *list.Element) bool {
	return nextWithLoop(board, element).Value != PLUS_SIGN
}

func nextWithLoop(board *list.List, element *list.Element) *list.Element {
	if (element.Next() != nil ) {
		return element.Next()
	}else {
		return board.Front()
	}
}

func prevWithLoop(board *list.List, element *list.Element) *list.Element {
	if (element.Prev() != nil ) {
		return element.Prev()
	}else {
		return board.Back()
	}
}

func isSurroundingSame(board *list.List, element *list.Element) bool {
	return nextWithLoop(board, element).Value == prevWithLoop(board, element).Value
}

func toList(board []int) *list.List {
	result := list.New()
	for _, element := range board {
		result.PushBack(int(element))
	}
	return result
}

func toArray(board *list.List) []int {
	array := make([]int, board.Len())
	i := 0
	for e := board.Front(); e != nil; e = e.Next() {
		array[i] = e.Value.(int)
		i += 1
	}
	return array
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 cpumanager

import (
	"fmt"
	"math"
	"sync"
	"time"

	cadvisorapi "github.com/google/cadvisor/info/v1"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"

	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/status"
)

// ActivePodsFunc is a function that returns a list of pods to reconcile.
type ActivePodsFunc func() []*v1.Pod

type runtimeService interface {
	UpdateContainerResources(id string, resources *runtimeapi.LinuxContainerResources) error
}

type policyName string

// cpuManagerStateFileName is the file name where cpu manager stores its state
const cpuManagerStateFileName = "cpu_manager_state"

// Manager interface provides methods for Kubelet to manage pod cpus.
type Manager interface {
	// Start is called during Kubelet initialization.
	Start(activePods ActivePodsFunc, podStatusProvider status.PodStatusProvider, containerRuntime runtimeService)

	// AddContainer is called between container create and container start
	// so that initial CPU affinity settings can be written through to the
	// container runtime before the first process begins to execute.
	AddContainer(p *v1.Pod, c *v1.Container, containerID string) error

	// RemoveContainer is called after Kubelet decides to kill or delete a
	// container. After this call, the CPU manager stops trying to reconcile
	// that container and any CPUs dedicated to the container are freed.
	RemoveContainer(containerID string) error

	// State returns a read-only interface to the internal CPU manager state.
	State() state.Reader
}

type manager struct {
	sync.Mutex
	policy Policy

	// reconcilePeriod is the duration between calls to reconcileState.
	reconcilePeriod time.Duration

	// state allows pluggable CPU assignment policies while sharing a common
	// representation of state for the system to inspect and reconcile.
	state state.State

	// containerRuntime is the container runtime service interface needed
	// to make UpdateContainerResources() calls against the containers.
	containerRuntime runtimeService

	// activePods is a method for listing active pods on the node
	// so all the containers can be updated in the reconciliation loop.
	activePods ActivePodsFunc

	// podStatusProvider provides a method for obtaining pod statuses
	// and the containerID of their containers
	podStatusProvider status.PodStatusProvider

	machineInfo *cadvisorapi.MachineInfo

	nodeAllocatableReservation v1.ResourceList
}

var _ Manager = &manager{}

// NewManager creates new cpu manager based on provided policy
func NewManager(cpuPolicyName string, reconcilePeriod time.Duration, machineInfo *cadvisorapi.MachineInfo, nodeAllocatableReservation v1.ResourceList, stateFileDirectory string) (Manager, error) {
	var policy Policy

	switch policyName(cpuPolicyName) {

	case PolicyNone:
		policy = NewNonePolicy()

	case PolicyStatic:
		topo, err := topology.Discover(machineInfo)
		if err != nil {
			return nil, err
		}
		klog.Infof("[cpumanager] detected CPU topology: %v", topo)
		reservedCPUs, ok := nodeAllocatableReservation[v1.ResourceCPU]
		if !ok {
			// The static policy cannot initialize without this information.
			return nil, fmt.Errorf("[cpumanager] unable to determine reserved CPU resources for static policy")
		}
		if reservedCPUs.IsZero() {
			// The static policy requires this to be nonzero. Zero CPU reservation
			// would allow the shared pool to be completely exhausted. At that point
			// either we would violate our guarantee of exclusivity or need to evict
			// any pod that has at least one container that requires zero CPUs.
			// See the comments in policy_static.go for more details.
			return nil, fmt.Errorf("[cpumanager] the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero")
		}

		// Take the ceiling of the reservation, since fractional CPUs cannot be
		// exclusively allocated.
		reservedCPUsFloat := float64(reservedCPUs.MilliValue()) / 1000
		numReservedCPUs := int(math.Ceil(reservedCPUsFloat))
		policy = NewStaticPolicy(topo, numReservedCPUs)

	default:
		klog.Errorf("[cpumanager] Unknown policy \"%s\", falling back to default policy \"%s\"", cpuPolicyName, PolicyNone)
		policy = NewNonePolicy()
	}

	stateImpl, err := state.NewCheckpointState(stateFileDirectory, cpuManagerStateFileName, policy.Name())
	if err != nil {
		return nil, fmt.Errorf("could not initialize checkpoint manager: %v", err)
	}

	manager := &manager{
		policy:                     policy,
		reconcilePeriod:            reconcilePeriod,
		state:                      stateImpl,
		machineInfo:                machineInfo,
		nodeAllocatableReservation: nodeAllocatableReservation,
	}
	return manager, nil
}

func (m *manager) Start(activePods ActivePodsFunc, podStatusProvider status.PodStatusProvider, containerRuntime runtimeService) {
	klog.Infof("[cpumanager] starting with %s policy", m.policy.Name())
	klog.Infof("[cpumanager] reconciling every %v", m.reconcilePeriod)

	m.activePods = activePods
	m.podStatusProvider = podStatusProvider
	m.containerRuntime = containerRuntime

	m.policy.Start(m.state)
	if m.policy.Name() == string(PolicyNone) {
		return
	}
	go wait.Until(func() { m.reconcileState() }, m.reconcilePeriod, wait.NeverStop)
}

func (m *manager) AddContainer(p *v1.Pod, c *v1.Container, containerID string) error {
	m.Lock()
	err := m.policy.AddContainer(m.state, p, c, containerID)
	if err != nil {
		klog.Errorf("[cpumanager] AddContainer error: %v", err)
		m.Unlock()
		return err
	}
	cpus := m.state.GetCPUSetOrDefault(containerID)
	m.Unlock()

	if !cpus.IsEmpty() {
		err = m.updateContainerCPUSet(containerID, cpus)
		if err != nil {
			klog.Errorf("[cpumanager] AddContainer error: %v", err)
			m.Lock()
			err := m.policy.RemoveContainer(m.state, containerID)
			if err != nil {
				klog.Errorf("[cpumanager] AddContainer rollback state error: %v", err)
			}
			m.Unlock()
		}
		return err
	}
	klog.V(5).Infof("[cpumanager] update container resources is skipped due to cpu set is empty")
	return nil
}

func (m *manager) RemoveContainer(containerID string) error {
	m.Lock()
	defer m.Unlock()

	err := m.policy.RemoveContainer(m.state, containerID)
	if err != nil {
		klog.Errorf("[cpumanager] RemoveContainer error: %v", err)
		return err
	}
	return nil
}

func (m *manager) State() state.Reader {
	return m.state
}

type reconciledContainer struct {
	podName       string
	containerName string
	containerID   string
}

func (m *manager) reconcileState() (success []reconciledContainer, failure []reconciledContainer) {
	success = []reconciledContainer{}
	failure = []reconciledContainer{}

	activeContainers := make(map[string]*v1.Pod)

	for _, pod := range m.activePods() {
		allContainers := pod.Spec.InitContainers
		allContainers = append(allContainers, pod.Spec.Containers...)
		for _, container := range allContainers {
			status, ok := m.podStatusProvider.GetPodStatus(pod.UID)
			if !ok {
				klog.Warningf("[cpumanager] reconcileState: skipping pod; status not found (pod: %s, container: %s)", pod.Name, container.Name)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, ""})
				break
			}

			containerID, err := findContainerIDByName(&status, container.Name)
			if err != nil {
				klog.Warningf("[cpumanager] reconcileState: skipping container; ID not found in status (pod: %s, container: %s, error: %v)", pod.Name, container.Name, err)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, ""})
				continue
			}

			// Check whether container is present in state, there may be 3 reasons why it's not present:
			// - policy does not want to track the container
			// - kubelet has just been restarted - and there is no previous state file
			// - container has been removed from state by RemoveContainer call (DeletionTimestamp is set)
			if _, ok := m.state.GetCPUSet(containerID); !ok {
				if status.Phase == v1.PodRunning && pod.DeletionTimestamp == nil {
					klog.V(4).Infof("[cpumanager] reconcileState: container is not present in state - trying to add (pod: %s, container: %s, container id: %s)", pod.Name, container.Name, containerID)
					err := m.AddContainer(pod, &container, containerID)
					if err != nil {
						klog.Errorf("[cpumanager] reconcileState: failed to add container (pod: %s, container: %s, container id: %s, error: %v)", pod.Name, container.Name, containerID, err)
						failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
						continue
					}
				} else {
					// if DeletionTimestamp is set, pod has already been removed from state
					// skip the pod/container since it's not running and will be deleted soon
					continue
				}
			}

			activeContainers[containerID] = pod

			cset := m.state.GetCPUSetOrDefault(containerID)
			if cset.IsEmpty() {
				// NOTE: This should not happen outside of tests.
				klog.Infof("[cpumanager] reconcileState: skipping container; assigned cpuset is empty (pod: %s, container: %s)", pod.Name, container.Name)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
				continue
			}

			klog.V(4).Infof("[cpumanager] reconcileState: updating container (pod: %s, container: %s, container id: %s, cpuset: \"%v\")", pod.Name, container.Name, containerID, cset)
			err = m.updateContainerCPUSet(containerID, cset)
			if err != nil {
				klog.Errorf("[cpumanager] reconcileState: failed to update container (pod: %s, container: %s, container id: %s, cpuset: \"%v\", error: %v)", pod.Name, container.Name, containerID, cset, err)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
				continue
			}
			success = append(success, reconciledContainer{pod.Name, container.Name, containerID})
		}
	}

	for containerID := range m.state.GetCPUAssignments() {
		if pod, ok := activeContainers[containerID]; !ok {
			err := m.RemoveContainer(containerID)
			if err != nil {
				klog.Errorf("[cpumanager] reconcileState: failed to remove container (pod: %s, container id: %s, error: %v)", pod.Name, containerID, err)
				failure = append(failure, reconciledContainer{pod.Name, "", containerID})
			}
		}
	}
	return success, failure
}

func findContainerIDByName(status *v1.PodStatus, name string) (string, error) {
	for _, container := range status.ContainerStatuses {
		if container.Name == name && container.ContainerID != "" {
			cid := &kubecontainer.ContainerID{}
			err := cid.ParseString(container.ContainerID)
			if err != nil {
				return "", err
			}
			return cid.ID, nil
		}
	}
	return "", fmt.Errorf("unable to find ID for container with name %v in pod status (it may not be running)", name)
}

func (m *manager) updateContainerCPUSet(containerID string, cpus cpuset.CPUSet) error {
	// TODO: Consider adding a `ResourceConfigForContainer` helper in
	// helpers_linux.go similar to what exists for pods.
	// It would be better to pass the full container resources here instead of
	// this patch-like partial resources.
	return m.containerRuntime.UpdateContainerResources(
		containerID,
		&runtimeapi.LinuxContainerResources{
			CpusetCpus: cpus.String(),
		})
}
<commit_msg>Query pod status outside loop over containers<commit_after>/*
Copyright 2017 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 cpumanager

import (
	"fmt"
	"math"
	"sync"
	"time"

	cadvisorapi "github.com/google/cadvisor/info/v1"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"

	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology"
	"k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/status"
)

// ActivePodsFunc is a function that returns a list of pods to reconcile.
type ActivePodsFunc func() []*v1.Pod

type runtimeService interface {
	UpdateContainerResources(id string, resources *runtimeapi.LinuxContainerResources) error
}

type policyName string

// cpuManagerStateFileName is the file name where cpu manager stores its state
const cpuManagerStateFileName = "cpu_manager_state"

// Manager interface provides methods for Kubelet to manage pod cpus.
type Manager interface {
	// Start is called during Kubelet initialization.
	Start(activePods ActivePodsFunc, podStatusProvider status.PodStatusProvider, containerRuntime runtimeService)

	// AddContainer is called between container create and container start
	// so that initial CPU affinity settings can be written through to the
	// container runtime before the first process begins to execute.
	AddContainer(p *v1.Pod, c *v1.Container, containerID string) error

	// RemoveContainer is called after Kubelet decides to kill or delete a
	// container. After this call, the CPU manager stops trying to reconcile
	// that container and any CPUs dedicated to the container are freed.
	RemoveContainer(containerID string) error

	// State returns a read-only interface to the internal CPU manager state.
	State() state.Reader
}

type manager struct {
	sync.Mutex
	policy Policy

	// reconcilePeriod is the duration between calls to reconcileState.
	reconcilePeriod time.Duration

	// state allows pluggable CPU assignment policies while sharing a common
	// representation of state for the system to inspect and reconcile.
	state state.State

	// containerRuntime is the container runtime service interface needed
	// to make UpdateContainerResources() calls against the containers.
	containerRuntime runtimeService

	// activePods is a method for listing active pods on the node
	// so all the containers can be updated in the reconciliation loop.
	activePods ActivePodsFunc

	// podStatusProvider provides a method for obtaining pod statuses
	// and the containerID of their containers
	podStatusProvider status.PodStatusProvider

	machineInfo *cadvisorapi.MachineInfo

	nodeAllocatableReservation v1.ResourceList
}

var _ Manager = &manager{}

// NewManager creates new cpu manager based on provided policy
func NewManager(cpuPolicyName string, reconcilePeriod time.Duration, machineInfo *cadvisorapi.MachineInfo, nodeAllocatableReservation v1.ResourceList, stateFileDirectory string) (Manager, error) {
	var policy Policy

	switch policyName(cpuPolicyName) {

	case PolicyNone:
		policy = NewNonePolicy()

	case PolicyStatic:
		topo, err := topology.Discover(machineInfo)
		if err != nil {
			return nil, err
		}
		klog.Infof("[cpumanager] detected CPU topology: %v", topo)
		reservedCPUs, ok := nodeAllocatableReservation[v1.ResourceCPU]
		if !ok {
			// The static policy cannot initialize without this information.
			return nil, fmt.Errorf("[cpumanager] unable to determine reserved CPU resources for static policy")
		}
		if reservedCPUs.IsZero() {
			// The static policy requires this to be nonzero. Zero CPU reservation
			// would allow the shared pool to be completely exhausted. At that point
			// either we would violate our guarantee of exclusivity or need to evict
			// any pod that has at least one container that requires zero CPUs.
			// See the comments in policy_static.go for more details.
			return nil, fmt.Errorf("[cpumanager] the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero")
		}

		// Take the ceiling of the reservation, since fractional CPUs cannot be
		// exclusively allocated.
		reservedCPUsFloat := float64(reservedCPUs.MilliValue()) / 1000
		numReservedCPUs := int(math.Ceil(reservedCPUsFloat))
		policy = NewStaticPolicy(topo, numReservedCPUs)

	default:
		klog.Errorf("[cpumanager] Unknown policy \"%s\", falling back to default policy \"%s\"", cpuPolicyName, PolicyNone)
		policy = NewNonePolicy()
	}

	stateImpl, err := state.NewCheckpointState(stateFileDirectory, cpuManagerStateFileName, policy.Name())
	if err != nil {
		return nil, fmt.Errorf("could not initialize checkpoint manager: %v", err)
	}

	manager := &manager{
		policy:                     policy,
		reconcilePeriod:            reconcilePeriod,
		state:                      stateImpl,
		machineInfo:                machineInfo,
		nodeAllocatableReservation: nodeAllocatableReservation,
	}
	return manager, nil
}

func (m *manager) Start(activePods ActivePodsFunc, podStatusProvider status.PodStatusProvider, containerRuntime runtimeService) {
	klog.Infof("[cpumanager] starting with %s policy", m.policy.Name())
	klog.Infof("[cpumanager] reconciling every %v", m.reconcilePeriod)

	m.activePods = activePods
	m.podStatusProvider = podStatusProvider
	m.containerRuntime = containerRuntime

	m.policy.Start(m.state)
	if m.policy.Name() == string(PolicyNone) {
		return
	}
	go wait.Until(func() { m.reconcileState() }, m.reconcilePeriod, wait.NeverStop)
}

func (m *manager) AddContainer(p *v1.Pod, c *v1.Container, containerID string) error {
	m.Lock()
	err := m.policy.AddContainer(m.state, p, c, containerID)
	if err != nil {
		klog.Errorf("[cpumanager] AddContainer error: %v", err)
		m.Unlock()
		return err
	}
	cpus := m.state.GetCPUSetOrDefault(containerID)
	m.Unlock()

	if !cpus.IsEmpty() {
		err = m.updateContainerCPUSet(containerID, cpus)
		if err != nil {
			klog.Errorf("[cpumanager] AddContainer error: %v", err)
			m.Lock()
			err := m.policy.RemoveContainer(m.state, containerID)
			if err != nil {
				klog.Errorf("[cpumanager] AddContainer rollback state error: %v", err)
			}
			m.Unlock()
		}
		return err
	}
	klog.V(5).Infof("[cpumanager] update container resources is skipped due to cpu set is empty")
	return nil
}

func (m *manager) RemoveContainer(containerID string) error {
	m.Lock()
	defer m.Unlock()

	err := m.policy.RemoveContainer(m.state, containerID)
	if err != nil {
		klog.Errorf("[cpumanager] RemoveContainer error: %v", err)
		return err
	}
	return nil
}

func (m *manager) State() state.Reader {
	return m.state
}

type reconciledContainer struct {
	podName       string
	containerName string
	containerID   string
}

func (m *manager) reconcileState() (success []reconciledContainer, failure []reconciledContainer) {
	success = []reconciledContainer{}
	failure = []reconciledContainer{}

	activeContainers := make(map[string]*v1.Pod)

	for _, pod := range m.activePods() {
		allContainers := pod.Spec.InitContainers
		allContainers = append(allContainers, pod.Spec.Containers...)
		status, ok := m.podStatusProvider.GetPodStatus(pod.UID)
		for _, container := range allContainers {
			if !ok {
				klog.Warningf("[cpumanager] reconcileState: skipping pod; status not found (pod: %s, container: %s)", pod.Name, container.Name)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, ""})
				break
			}

			containerID, err := findContainerIDByName(&status, container.Name)
			if err != nil {
				klog.Warningf("[cpumanager] reconcileState: skipping container; ID not found in status (pod: %s, container: %s, error: %v)", pod.Name, container.Name, err)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, ""})
				continue
			}

			// Check whether container is present in state, there may be 3 reasons why it's not present:
			// - policy does not want to track the container
			// - kubelet has just been restarted - and there is no previous state file
			// - container has been removed from state by RemoveContainer call (DeletionTimestamp is set)
			if _, ok := m.state.GetCPUSet(containerID); !ok {
				if status.Phase == v1.PodRunning && pod.DeletionTimestamp == nil {
					klog.V(4).Infof("[cpumanager] reconcileState: container is not present in state - trying to add (pod: %s, container: %s, container id: %s)", pod.Name, container.Name, containerID)
					err := m.AddContainer(pod, &container, containerID)
					if err != nil {
						klog.Errorf("[cpumanager] reconcileState: failed to add container (pod: %s, container: %s, container id: %s, error: %v)", pod.Name, container.Name, containerID, err)
						failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
						continue
					}
				} else {
					// if DeletionTimestamp is set, pod has already been removed from state
					// skip the pod/container since it's not running and will be deleted soon
					continue
				}
			}

			activeContainers[containerID] = pod

			cset := m.state.GetCPUSetOrDefault(containerID)
			if cset.IsEmpty() {
				// NOTE: This should not happen outside of tests.
				klog.Infof("[cpumanager] reconcileState: skipping container; assigned cpuset is empty (pod: %s, container: %s)", pod.Name, container.Name)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
				continue
			}

			klog.V(4).Infof("[cpumanager] reconcileState: updating container (pod: %s, container: %s, container id: %s, cpuset: \"%v\")", pod.Name, container.Name, containerID, cset)
			err = m.updateContainerCPUSet(containerID, cset)
			if err != nil {
				klog.Errorf("[cpumanager] reconcileState: failed to update container (pod: %s, container: %s, container id: %s, cpuset: \"%v\", error: %v)", pod.Name, container.Name, containerID, cset, err)
				failure = append(failure, reconciledContainer{pod.Name, container.Name, containerID})
				continue
			}
			success = append(success, reconciledContainer{pod.Name, container.Name, containerID})
		}
	}

	for containerID := range m.state.GetCPUAssignments() {
		if pod, ok := activeContainers[containerID]; !ok {
			err := m.RemoveContainer(containerID)
			if err != nil {
				klog.Errorf("[cpumanager] reconcileState: failed to remove container (pod: %s, container id: %s, error: %v)", pod.Name, containerID, err)
				failure = append(failure, reconciledContainer{pod.Name, "", containerID})
			}
		}
	}
	return success, failure
}

func findContainerIDByName(status *v1.PodStatus, name string) (string, error) {
	for _, container := range status.ContainerStatuses {
		if container.Name == name && container.ContainerID != "" {
			cid := &kubecontainer.ContainerID{}
			err := cid.ParseString(container.ContainerID)
			if err != nil {
				return "", err
			}
			return cid.ID, nil
		}
	}
	return "", fmt.Errorf("unable to find ID for container with name %v in pod status (it may not be running)", name)
}

func (m *manager) updateContainerCPUSet(containerID string, cpus cpuset.CPUSet) error {
	// TODO: Consider adding a `ResourceConfigForContainer` helper in
	// helpers_linux.go similar to what exists for pods.
	// It would be better to pass the full container resources here instead of
	// this patch-like partial resources.
	return m.containerRuntime.UpdateContainerResources(
		containerID,
		&runtimeapi.LinuxContainerResources{
			CpusetCpus: cpus.String(),
		})
}
<|endoftext|>


<commit_before>package notifiers

import (
	"encoding/json"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "teams",
		Name:        "Microsoft Teams",
		Description: "Sends notifications using Incoming Webhook connector to Microsoft Teams",
		Factory:     NewTeamsNotifier,
		OptionsTemplate: `

Teams settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Teams incoming webhook url"></input>

    `,
	})

}

func NewTeamsNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	return &TeamsNotifier{
		NotifierBase: NewNotifierBase(model),
		Url:          url,
		log:          log.New("alerting.notifier.teams"),
	}, nil
}

type TeamsNotifier struct {
	NotifierBase
	Url string
	log log.Logger
}

func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("Failed get rule link", "error", err)
		return err
	}

	fields := make([]map[string]interface{}, 0)
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"name":  evt.Metric,
			"value": evt.Value,
		})
		if index > fieldLimitCount {
			break
		}
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"name":  "Error message",
			"value": evalContext.Error.Error(),
		})
	}

	message := ""
	if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok.
		message = evalContext.Rule.Message
	}

	body := map[string]interface{}{
		"@type":    "MessageCard",
		"@context": "http://schema.org/extensions",
		// summary MUST not be empty or the webhook request fails
		// summary SHOULD contain some meaningful information, since it is used for mobile notifications
		"summary":    evalContext.GetNotificationTitle(),
		"title":      evalContext.GetNotificationTitle(),
		"themeColor": evalContext.GetStateModel().Color,
		"sections": []map[string]interface{}{
			{
				"title": "Details",
				"facts": fields,
				"images": []map[string]interface{}{
					{
						"image": evalContext.ImagePublicUrl,
					},
				},
				"text": message,
			},
		},
		"potentialAction": []map[string]interface{}{
			{
				"@context": "http://schema.org",
				"@type":    "OpenUri",
				"name":     "View Rule",
				"targets": []map[string]interface{}{
					{
						"os": "default", "uri": ruleUrl,
					},
				},
			},
			{
				"@context": "http://schema.org",
				"@type":    "OpenUri",
				"name":     "View Graph",
				"targets": []map[string]interface{}{
					{
						"os": "default", "uri": evalContext.ImagePublicUrl,
					},
				},
			},
		},
	}

	data, _ := json.Marshal(&body)
	cmd := &m.SendWebhookSync{Url: this.Url, Body: string(data)}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send teams notification", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<commit_msg>Don't include non-existing image in MS Teams alert<commit_after>package notifiers

import (
	"encoding/json"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "teams",
		Name:        "Microsoft Teams",
		Description: "Sends notifications using Incoming Webhook connector to Microsoft Teams",
		Factory:     NewTeamsNotifier,
		OptionsTemplate: `

Teams settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Teams incoming webhook url"></input>

    `,
	})

}

func NewTeamsNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	return &TeamsNotifier{
		NotifierBase: NewNotifierBase(model),
		Url:          url,
		log:          log.New("alerting.notifier.teams"),
	}, nil
}

type TeamsNotifier struct {
	NotifierBase
	Url string
	log log.Logger
}

func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("Failed get rule link", "error", err)
		return err
	}

	fields := make([]map[string]interface{}, 0)
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"name":  evt.Metric,
			"value": evt.Value,
		})
		if index > fieldLimitCount {
			break
		}
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"name":  "Error message",
			"value": evalContext.Error.Error(),
		})
	}

	message := ""
	if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok.
		message = evalContext.Rule.Message
	}

	images := ""
	if evalContext.ImagePublicUrl != "" {
		images = []map[string]interface{}{
			{
				"image": evalContext.ImagePublicUrl
			}
		}
	}

	body := map[string]interface{}{
		"@type":    "MessageCard",
		"@context": "http://schema.org/extensions",
		// summary MUST not be empty or the webhook request fails
		// summary SHOULD contain some meaningful information, since it is used for mobile notifications
		"summary":    evalContext.GetNotificationTitle(),
		"title":      evalContext.GetNotificationTitle(),
		"themeColor": evalContext.GetStateModel().Color,
		"sections": []map[string]interface{}{
			{
				"title": "Details",
				"facts": fields,
				"images": images,
				"text": message,
			},
		},
		"potentialAction": []map[string]interface{}{
			{
				"@context": "http://schema.org",
				"@type":    "OpenUri",
				"name":     "View Rule",
				"targets": []map[string]interface{}{
					{
						"os": "default", "uri": ruleUrl,
					},
				},
			},
			{
				"@context": "http://schema.org",
				"@type":    "OpenUri",
				"name":     "View Graph",
				"targets": []map[string]interface{}{
					{
						"os": "default", "uri": evalContext.ImagePublicUrl,
					},
				},
			},
		},
	}

	data, _ := json.Marshal(&body)
	cmd := &m.SendWebhookSync{Url: this.Url, Body: string(data)}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send teams notification", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package apply

import (
	"context"
	"fmt"

	"k8s.io/apimachinery/pkg/runtime"

	"github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/kubevirt/pkg/controller"

	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"kubevirt.io/client-go/log"
	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/rbac"
)

func (r *Reconciler) createOrUpdateClusterRole(cr *rbacv1.ClusterRole, imageTag string, imageRegistry string, id string) error {
	return rbacCreateOrUpdate(r, cr, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateClusterRoleBinding(crb *rbacv1.ClusterRoleBinding, imageTag string, imageRegistry string, id string) error {
	return rbacCreateOrUpdate(r, crb, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateRole(role *rbacv1.Role, imageTag string, imageRegistry string, id string) error {
	if !r.stores.ServiceMonitorEnabled && (role.Name == rbac.MONITOR_SERVICEACCOUNT_NAME) {
		return nil
	}

	return rbacCreateOrUpdate(r, role, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateRoleBinding(rb *rbacv1.RoleBinding, imageTag string, imageRegistry string, id string) error {
	if !r.stores.ServiceMonitorEnabled && (rb.Name == rbac.MONITOR_SERVICEACCOUNT_NAME) {
		return nil
	}

	return rbacCreateOrUpdate(r, rb, imageTag, imageRegistry, id)
}

func rbacCreateOrUpdate(r *Reconciler, required runtime.Object, imageTag, imageRegistry, id string) (err error) {

	roleTypeName := required.GetObjectKind().GroupVersionKind().Kind

	cachedRoleInterface, exists, _ := getRbacCache(r, required).Get(required)
	requiredMeta := getRbacMetaObject(required)

	injectOperatorMetadata(r.kv, requiredMeta, imageTag, imageRegistry, id, true)
	if !exists {
		// Create non existent
		err = getRbacCreateFunction(r, required)()
		if err != nil {
			return fmt.Errorf("unable to create %v %+v: %v", roleTypeName, required, err)
		}
		log.Log.V(2).Infof("%v %v created", roleTypeName, requiredMeta.GetName())
		return nil
	}

	metaChanged := resourcemerge.BoolPtr(false)
	existingCopy := cachedRoleInterface.(runtime.Object).DeepCopyObject()
	existingCopyMeta := getRbacMetaObject(existingCopy)

	resourcemerge.EnsureObjectMeta(metaChanged, existingCopyMeta, *requiredMeta)
	enforceAPIGroup(existingCopy, required)

	specChanged := changeRbacExistingByRequired(existingCopy, required)

	if !*metaChanged && !specChanged {
		log.Log.V(4).Infof("%v %v already exists", roleTypeName, requiredMeta.GetName())
		return nil
	}

	// Update existing, we don't need to patch for rbac rules.
	err = getRbacUpdateFunction(r, existingCopy)()
	if err != nil {
		return fmt.Errorf("unable to update %v %+v: %v", roleTypeName, required, err)
	}
	log.Log.V(2).Infof("%v %v updated", roleTypeName, requiredMeta.GetName())

	return nil
}

func getRbacCreateFunction(r *Reconciler, obj runtime.Object) (createFunc func() error) {

	rbacObj := r.clientset.RbacV1()
	namespace := r.kv.Namespace

	raiseExpectation := func(exp *controller.UIDTrackingControllerExpectations) {
		exp.RaiseExpectations(r.kvKey, 1, 0)
	}
	lowerExpectationIfErr := func(exp *controller.UIDTrackingControllerExpectations, err error) {
		if err != nil {
			exp.LowerExpectations(r.kvKey, 1, 0)
		}
	}

	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)

		createFunc = func() error {
			raiseExpectation(r.expectations.Role)
			_, err := rbacObj.Roles(namespace).Create(context.Background(), role, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.Role, err)
			return err
		}
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)

		createFunc = func() error {
			raiseExpectation(r.expectations.ClusterRole)
			_, err := rbacObj.ClusterRoles().Create(context.Background(), role, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.ClusterRole, err)
			return err
		}
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)

		createFunc = func() error {
			raiseExpectation(r.expectations.RoleBinding)
			_, err := rbacObj.RoleBindings(namespace).Create(context.Background(), roleBinding, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.RoleBinding, err)
			return err
		}
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)

		createFunc = func() error {
			raiseExpectation(r.expectations.ClusterRoleBinding)
			_, err := rbacObj.ClusterRoleBindings().Create(context.Background(), roleBinding, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.ClusterRoleBinding, err)
			return err
		}
	}

	return
}

func getRbacUpdateFunction(r *Reconciler, obj runtime.Object) (updateFunc func() (err error)) {
	rbacObj := r.clientset.RbacV1()
	namespace := r.kv.Namespace

	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)

		updateFunc = func() (err error) {
			_, err = rbacObj.Roles(namespace).Update(context.Background(), role, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)

		updateFunc = func() (err error) {
			_, err = rbacObj.ClusterRoles().Update(context.Background(), role, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)

		updateFunc = func() (err error) {
			_, err = rbacObj.RoleBindings(namespace).Update(context.Background(), roleBinding, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)

		updateFunc = func() (err error) {
			_, err = rbacObj.ClusterRoleBindings().Update(context.Background(), roleBinding, metav1.UpdateOptions{})
			return err
		}
	}

	return
}

func getRbacMetaObject(obj runtime.Object) (meta *metav1.ObjectMeta) {
	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)
		meta = &role.ObjectMeta
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)
		meta = &role.ObjectMeta
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)
		meta = &roleBinding.ObjectMeta
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)
		meta = &roleBinding.ObjectMeta
	}

	return
}

func enforceAPIGroup(existing runtime.Object, required runtime.Object) {
	var existingRoleRef *rbacv1.RoleRef
	var requiredRoleRef *rbacv1.RoleRef
	var existingSubjects []rbacv1.Subject
	var requiredSubjects []rbacv1.Subject

	switch required.(type) {
	case *rbacv1.RoleBinding:
		crExisting := existing.(*rbacv1.RoleBinding)
		crRequired := required.(*rbacv1.RoleBinding)
		existingRoleRef = &crExisting.RoleRef
		requiredRoleRef = &crRequired.RoleRef
		existingSubjects = crExisting.Subjects
		requiredSubjects = crRequired.Subjects
	case *rbacv1.ClusterRoleBinding:
		crbExisting := existing.(*rbacv1.ClusterRoleBinding)
		crbRequired := required.(*rbacv1.ClusterRoleBinding)
		existingRoleRef = &crbExisting.RoleRef
		requiredRoleRef = &crbRequired.RoleRef
		existingSubjects = crbExisting.Subjects
		requiredSubjects = crbRequired.Subjects
	default:
		return
	}

	existingRoleRef.APIGroup = rbacv1.GroupName
	for i := range existingSubjects {
		if existingSubjects[i].Kind == "User" {
			existingSubjects[i].APIGroup = rbacv1.GroupName
		}
	}

	requiredRoleRef.APIGroup = rbacv1.GroupName
	for i := range requiredSubjects {
		if existingSubjects[i].Kind == "User" {
			requiredSubjects[i].APIGroup = rbacv1.GroupName
		}
	}
}

func changeRbacExistingByRequired(existing runtime.Object, required runtime.Object) (modified bool) {
	// This is to avoid using reflections for performance reasons
	arePolicyRulesEqual := func(pr1, pr2 []rbacv1.PolicyRule) bool {
		if len(pr1) != len(pr2) {
			return false
		}

		areStringListsEqual := func(strList1 []string, strList2 []string) bool {
			if len(strList1) != len(strList2) {
				return false
			}
			for i := range strList1 {
				if strList1[i] != strList2[i] {
					return false
				}
			}
			return true
		}

		for i := range pr1 {
			if !areStringListsEqual(pr1[i].Verbs, pr2[i].Verbs) || !areStringListsEqual(pr1[i].Resources, pr2[i].Resources) ||
				!areStringListsEqual(pr1[i].APIGroups, pr2[i].APIGroups) || !areStringListsEqual(pr1[i].NonResourceURLs, pr2[i].NonResourceURLs) ||
				!areStringListsEqual(pr1[i].ResourceNames, pr2[i].ResourceNames) {
				return false
			}
		}

		return true
	}
	changeExistingPolicyRulesByRequired := func(existing, required *[]rbacv1.PolicyRule) (modified bool) {
		if !arePolicyRulesEqual(*existing, *required) {
			*existing = *required
			return true
		}
		return false
	}
	changeExistingSubjectsByRequired := func(existingSubjects, requiredSubjects *[]rbacv1.Subject) bool {
		modified := false
		if len(*existingSubjects) != len(*requiredSubjects) {
			*existingSubjects = *requiredSubjects
			return false
		}

		for _, existingSubject := range *existingSubjects {
			found := false

			for _, requiredSubject := range *requiredSubjects {
				if existingSubject == requiredSubject {
					found = true
					break
				}
			}

			if !found {
				modified = true
				break
			}
		}

		if modified {
			*existingSubjects = *requiredSubjects
		}
		return modified
	}
	changeExistingRoleRefByRequired := func(existingRoleRef, requiredRoleRef *rbacv1.RoleRef) (modified bool) {
		if *existingRoleRef != *requiredRoleRef {
			*existingRoleRef = *requiredRoleRef
			return true
		}

		return false
	}

	switch existing.(type) {
	case *rbacv1.Role:
		existingRole := existing.(*rbacv1.Role)
		requiredRole := required.(*rbacv1.Role)
		modified = changeExistingPolicyRulesByRequired(&existingRole.Rules, &requiredRole.Rules)
	case *rbacv1.ClusterRole:
		existingClusterRole := existing.(*rbacv1.ClusterRole)
		requiredClusterRole := required.(*rbacv1.ClusterRole)
		modified = changeExistingPolicyRulesByRequired(&existingClusterRole.Rules, &requiredClusterRole.Rules)
	case *rbacv1.RoleBinding:
		existingRoleBinding := existing.(*rbacv1.RoleBinding)
		requiredRoleBinding := required.(*rbacv1.RoleBinding)
		modified = changeExistingSubjectsByRequired(&existingRoleBinding.Subjects, &requiredRoleBinding.Subjects)
		modified = changeExistingRoleRefByRequired(&existingRoleBinding.RoleRef, &requiredRoleBinding.RoleRef) || modified
	case *rbacv1.ClusterRoleBinding:
		existingClusterRoleBinding := existing.(*rbacv1.ClusterRoleBinding)
		requiredClusterRoleBinding := required.(*rbacv1.ClusterRoleBinding)
		modified = changeExistingSubjectsByRequired(&existingClusterRoleBinding.Subjects, &requiredClusterRoleBinding.Subjects)
		modified = changeExistingRoleRefByRequired(&existingClusterRoleBinding.RoleRef, &requiredClusterRoleBinding.RoleRef) || modified
	}

	return modified
}

func getRbacCache(r *Reconciler, obj runtime.Object) (cache cache.Store) {
	switch obj.(type) {
	case *rbacv1.Role:
		cache = r.stores.RoleCache
	case *rbacv1.ClusterRole:
		cache = r.stores.ClusterRoleCache
	case *rbacv1.RoleBinding:
		cache = r.stores.RoleBindingCache
	case *rbacv1.ClusterRoleBinding:
		cache = r.stores.ClusterRoleBindingCache
	}

	return cache
}
<commit_msg>[rbac.go]: Small fix - wrong array being used<commit_after>package apply

import (
	"context"
	"fmt"

	"k8s.io/apimachinery/pkg/runtime"

	"github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/kubevirt/pkg/controller"

	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"kubevirt.io/client-go/log"
	"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/rbac"
)

func (r *Reconciler) createOrUpdateClusterRole(cr *rbacv1.ClusterRole, imageTag string, imageRegistry string, id string) error {
	return rbacCreateOrUpdate(r, cr, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateClusterRoleBinding(crb *rbacv1.ClusterRoleBinding, imageTag string, imageRegistry string, id string) error {
	return rbacCreateOrUpdate(r, crb, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateRole(role *rbacv1.Role, imageTag string, imageRegistry string, id string) error {
	if !r.stores.ServiceMonitorEnabled && (role.Name == rbac.MONITOR_SERVICEACCOUNT_NAME) {
		return nil
	}

	return rbacCreateOrUpdate(r, role, imageTag, imageRegistry, id)
}

func (r *Reconciler) createOrUpdateRoleBinding(rb *rbacv1.RoleBinding, imageTag string, imageRegistry string, id string) error {
	if !r.stores.ServiceMonitorEnabled && (rb.Name == rbac.MONITOR_SERVICEACCOUNT_NAME) {
		return nil
	}

	return rbacCreateOrUpdate(r, rb, imageTag, imageRegistry, id)
}

func rbacCreateOrUpdate(r *Reconciler, required runtime.Object, imageTag, imageRegistry, id string) (err error) {

	roleTypeName := required.GetObjectKind().GroupVersionKind().Kind

	cachedRoleInterface, exists, _ := getRbacCache(r, required).Get(required)
	requiredMeta := getRbacMetaObject(required)

	injectOperatorMetadata(r.kv, requiredMeta, imageTag, imageRegistry, id, true)
	if !exists {
		// Create non existent
		err = getRbacCreateFunction(r, required)()
		if err != nil {
			return fmt.Errorf("unable to create %v %+v: %v", roleTypeName, required, err)
		}
		log.Log.V(2).Infof("%v %v created", roleTypeName, requiredMeta.GetName())
		return nil
	}

	metaChanged := resourcemerge.BoolPtr(false)
	existingCopy := cachedRoleInterface.(runtime.Object).DeepCopyObject()
	existingCopyMeta := getRbacMetaObject(existingCopy)

	resourcemerge.EnsureObjectMeta(metaChanged, existingCopyMeta, *requiredMeta)
	enforceAPIGroup(existingCopy, required)

	specChanged := changeRbacExistingByRequired(existingCopy, required)

	if !*metaChanged && !specChanged {
		log.Log.V(4).Infof("%v %v already exists", roleTypeName, requiredMeta.GetName())
		return nil
	}

	// Update existing, we don't need to patch for rbac rules.
	err = getRbacUpdateFunction(r, existingCopy)()
	if err != nil {
		return fmt.Errorf("unable to update %v %+v: %v", roleTypeName, required, err)
	}
	log.Log.V(2).Infof("%v %v updated", roleTypeName, requiredMeta.GetName())

	return nil
}

func getRbacCreateFunction(r *Reconciler, obj runtime.Object) (createFunc func() error) {

	rbacObj := r.clientset.RbacV1()
	namespace := r.kv.Namespace

	raiseExpectation := func(exp *controller.UIDTrackingControllerExpectations) {
		exp.RaiseExpectations(r.kvKey, 1, 0)
	}
	lowerExpectationIfErr := func(exp *controller.UIDTrackingControllerExpectations, err error) {
		if err != nil {
			exp.LowerExpectations(r.kvKey, 1, 0)
		}
	}

	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)

		createFunc = func() error {
			raiseExpectation(r.expectations.Role)
			_, err := rbacObj.Roles(namespace).Create(context.Background(), role, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.Role, err)
			return err
		}
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)

		createFunc = func() error {
			raiseExpectation(r.expectations.ClusterRole)
			_, err := rbacObj.ClusterRoles().Create(context.Background(), role, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.ClusterRole, err)
			return err
		}
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)

		createFunc = func() error {
			raiseExpectation(r.expectations.RoleBinding)
			_, err := rbacObj.RoleBindings(namespace).Create(context.Background(), roleBinding, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.RoleBinding, err)
			return err
		}
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)

		createFunc = func() error {
			raiseExpectation(r.expectations.ClusterRoleBinding)
			_, err := rbacObj.ClusterRoleBindings().Create(context.Background(), roleBinding, metav1.CreateOptions{})
			lowerExpectationIfErr(r.expectations.ClusterRoleBinding, err)
			return err
		}
	}

	return
}

func getRbacUpdateFunction(r *Reconciler, obj runtime.Object) (updateFunc func() (err error)) {
	rbacObj := r.clientset.RbacV1()
	namespace := r.kv.Namespace

	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)

		updateFunc = func() (err error) {
			_, err = rbacObj.Roles(namespace).Update(context.Background(), role, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)

		updateFunc = func() (err error) {
			_, err = rbacObj.ClusterRoles().Update(context.Background(), role, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)

		updateFunc = func() (err error) {
			_, err = rbacObj.RoleBindings(namespace).Update(context.Background(), roleBinding, metav1.UpdateOptions{})
			return err
		}
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)

		updateFunc = func() (err error) {
			_, err = rbacObj.ClusterRoleBindings().Update(context.Background(), roleBinding, metav1.UpdateOptions{})
			return err
		}
	}

	return
}

func getRbacMetaObject(obj runtime.Object) (meta *metav1.ObjectMeta) {
	switch obj.(type) {
	case *rbacv1.Role:
		role := obj.(*rbacv1.Role)
		meta = &role.ObjectMeta
	case *rbacv1.ClusterRole:
		role := obj.(*rbacv1.ClusterRole)
		meta = &role.ObjectMeta
	case *rbacv1.RoleBinding:
		roleBinding := obj.(*rbacv1.RoleBinding)
		meta = &roleBinding.ObjectMeta
	case *rbacv1.ClusterRoleBinding:
		roleBinding := obj.(*rbacv1.ClusterRoleBinding)
		meta = &roleBinding.ObjectMeta
	}

	return
}

func enforceAPIGroup(existing runtime.Object, required runtime.Object) {
	var existingRoleRef *rbacv1.RoleRef
	var requiredRoleRef *rbacv1.RoleRef
	var existingSubjects []rbacv1.Subject
	var requiredSubjects []rbacv1.Subject

	switch required.(type) {
	case *rbacv1.RoleBinding:
		crExisting := existing.(*rbacv1.RoleBinding)
		crRequired := required.(*rbacv1.RoleBinding)
		existingRoleRef = &crExisting.RoleRef
		requiredRoleRef = &crRequired.RoleRef
		existingSubjects = crExisting.Subjects
		requiredSubjects = crRequired.Subjects
	case *rbacv1.ClusterRoleBinding:
		crbExisting := existing.(*rbacv1.ClusterRoleBinding)
		crbRequired := required.(*rbacv1.ClusterRoleBinding)
		existingRoleRef = &crbExisting.RoleRef
		requiredRoleRef = &crbRequired.RoleRef
		existingSubjects = crbExisting.Subjects
		requiredSubjects = crbRequired.Subjects
	default:
		return
	}

	existingRoleRef.APIGroup = rbacv1.GroupName
	for i := range existingSubjects {
		if existingSubjects[i].Kind == "User" {
			existingSubjects[i].APIGroup = rbacv1.GroupName
		}
	}

	requiredRoleRef.APIGroup = rbacv1.GroupName
	for i := range requiredSubjects {
		if requiredSubjects[i].Kind == "User" {
			requiredSubjects[i].APIGroup = rbacv1.GroupName
		}
	}
}

func changeRbacExistingByRequired(existing runtime.Object, required runtime.Object) (modified bool) {
	// This is to avoid using reflections for performance reasons
	arePolicyRulesEqual := func(pr1, pr2 []rbacv1.PolicyRule) bool {
		if len(pr1) != len(pr2) {
			return false
		}

		areStringListsEqual := func(strList1 []string, strList2 []string) bool {
			if len(strList1) != len(strList2) {
				return false
			}
			for i := range strList1 {
				if strList1[i] != strList2[i] {
					return false
				}
			}
			return true
		}

		for i := range pr1 {
			if !areStringListsEqual(pr1[i].Verbs, pr2[i].Verbs) || !areStringListsEqual(pr1[i].Resources, pr2[i].Resources) ||
				!areStringListsEqual(pr1[i].APIGroups, pr2[i].APIGroups) || !areStringListsEqual(pr1[i].NonResourceURLs, pr2[i].NonResourceURLs) ||
				!areStringListsEqual(pr1[i].ResourceNames, pr2[i].ResourceNames) {
				return false
			}
		}

		return true
	}
	changeExistingPolicyRulesByRequired := func(existing, required *[]rbacv1.PolicyRule) (modified bool) {
		if !arePolicyRulesEqual(*existing, *required) {
			*existing = *required
			return true
		}
		return false
	}
	changeExistingSubjectsByRequired := func(existingSubjects, requiredSubjects *[]rbacv1.Subject) bool {
		modified := false
		if len(*existingSubjects) != len(*requiredSubjects) {
			*existingSubjects = *requiredSubjects
			return false
		}

		for _, existingSubject := range *existingSubjects {
			found := false

			for _, requiredSubject := range *requiredSubjects {
				if existingSubject == requiredSubject {
					found = true
					break
				}
			}

			if !found {
				modified = true
				break
			}
		}

		if modified {
			*existingSubjects = *requiredSubjects
		}
		return modified
	}
	changeExistingRoleRefByRequired := func(existingRoleRef, requiredRoleRef *rbacv1.RoleRef) (modified bool) {
		if *existingRoleRef != *requiredRoleRef {
			*existingRoleRef = *requiredRoleRef
			return true
		}

		return false
	}

	switch existing.(type) {
	case *rbacv1.Role:
		existingRole := existing.(*rbacv1.Role)
		requiredRole := required.(*rbacv1.Role)
		modified = changeExistingPolicyRulesByRequired(&existingRole.Rules, &requiredRole.Rules)
	case *rbacv1.ClusterRole:
		existingClusterRole := existing.(*rbacv1.ClusterRole)
		requiredClusterRole := required.(*rbacv1.ClusterRole)
		modified = changeExistingPolicyRulesByRequired(&existingClusterRole.Rules, &requiredClusterRole.Rules)
	case *rbacv1.RoleBinding:
		existingRoleBinding := existing.(*rbacv1.RoleBinding)
		requiredRoleBinding := required.(*rbacv1.RoleBinding)
		modified = changeExistingSubjectsByRequired(&existingRoleBinding.Subjects, &requiredRoleBinding.Subjects)
		modified = changeExistingRoleRefByRequired(&existingRoleBinding.RoleRef, &requiredRoleBinding.RoleRef) || modified
	case *rbacv1.ClusterRoleBinding:
		existingClusterRoleBinding := existing.(*rbacv1.ClusterRoleBinding)
		requiredClusterRoleBinding := required.(*rbacv1.ClusterRoleBinding)
		modified = changeExistingSubjectsByRequired(&existingClusterRoleBinding.Subjects, &requiredClusterRoleBinding.Subjects)
		modified = changeExistingRoleRefByRequired(&existingClusterRoleBinding.RoleRef, &requiredClusterRoleBinding.RoleRef) || modified
	}

	return modified
}

func getRbacCache(r *Reconciler, obj runtime.Object) (cache cache.Store) {
	switch obj.(type) {
	case *rbacv1.Role:
		cache = r.stores.RoleCache
	case *rbacv1.ClusterRole:
		cache = r.stores.ClusterRoleCache
	case *rbacv1.RoleBinding:
		cache = r.stores.RoleBindingCache
	case *rbacv1.ClusterRoleBinding:
		cache = r.stores.ClusterRoleBindingCache
	}

	return cache
}
<|endoftext|>