<commit_before>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package models

import (
	"fmt"
	"sort"
	"testing"
	"time"

	"github.com/Pallinder/go-randomdata"
	. "github.com/franela/goblin"
)

func TestClanModel(t *testing.T) {
	g := Goblin(t)
	testDb, err := GetTestDB()
	g.Assert(err == nil).IsTrue()
	faultyDb := GetFaultyTestDB()

	g.Describe("Clan Model", func() {
		g.Describe("Basic Operations", func() {
			g.It("Should sort clans by name", func() {
				_, clans, err := GetTestClans(testDb, "test", "test-sort-clan", 10)
				g.Assert(err == nil).IsTrue()

				sort.Sort(ClanByName(clans))

				for i := 0; i < 10; i++ {
					g.Assert(clans[i].Name).Equal(fmt.Sprintf("test-sort-clan-%d", i))
				}
			})

			g.It("Should create a new Clan", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				clan := &Clan{
					GameID:   "test",
					PublicID: "test-clan-2",
					Name:     "clan-name",
					Metadata: "{}",
					OwnerID:  player.ID,
				}
				err = testDb.Insert(clan)
				g.Assert(err == nil).IsTrue()
				g.Assert(clan.ID != 0).IsTrue()

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
			})

			g.It("Should update a Clan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dt := clan.UpdatedAt

				clan.Metadata = "{ \"x\": 1 }"
				count, err := testDb.Update(clan)
				g.Assert(err == nil).IsTrue()
				g.Assert(int(count)).Equal(1)
				g.Assert(clan.UpdatedAt > dt).IsTrue()
			})
		})

		g.Describe("Get By Id", func() {
			g.It("Should get existing Clan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
			})

			g.It("Should not get non-existing Clan", func() {
				_, err = GetClanByID(testDb, -1)
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: -1")
			})
		})

		g.Describe("Get By Public Id", func() {
			g.It("Should get an existing Clan by Game and PublicID", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByPublicID(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
			})

			g.It("Should not get a non-existing Clan by Game and PublicID", func() {
				_, err = GetClanByPublicID(testDb, "invalid-game", "invalid-clan")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: invalid-clan")
			})
		})

		g.Describe("Get By Public Id and OwnerPublicID", func() {
			g.It("Should get an existing Clan by Game, PublicID and OwnerPublicID", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByPublicIDAndOwnerPublicID(testDb, clan.GameID, clan.PublicID, player.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
				g.Assert(dbClan.Name).Equal(clan.Name)
				g.Assert(dbClan.OwnerID).Equal(clan.OwnerID)
			})

			g.It("Should not get a non-existing Clan by Game, PublicID and OwnerPublicID", func() {
				_, err = GetClanByPublicIDAndOwnerPublicID(testDb, "invalid-game", "invalid-clan", "invalid-owner-public-id")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: invalid-clan")
			})

			g.It("Should not get a existing Clan by Game, PublicID and OwnerPublicID if not Clan owner", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				_, err = GetClanByPublicIDAndOwnerPublicID(testDb, clan.GameID, clan.PublicID, "invalid-owner-public-id")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Clan was not found with id: %s", clan.PublicID))
			})
		})

		g.Describe("Create Clan", func() {
			g.It("Should create a new Clan with CreateClan", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				clan, err := CreateClan(
					testDb,
					player.GameID,
					"create-1",
					randomdata.FullName(randomdata.RandomGender),
					player.PublicID,
					"{}",
				)

				g.Assert(err == nil).IsTrue()
				g.Assert(clan.ID != 0).IsTrue()

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
			})

			g.It("Should not create a new Clan with CreateClan if invalid data", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				_, err = CreateClan(
					testDb,
					player.GameID,
					randomdata.FullName(randomdata.RandomGender),
					"clan-name",
					player.PublicID,
					"it-will-fail-because-metadata-is-not-a-json",
				)

				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("pq: invalid input syntax for type json")
			})

			g.It("Should not create a new Clan with CreateClan if unexistent player", func() {
				playerPublicID := randomdata.FullName(randomdata.RandomGender)
				_, err = CreateClan(
					testDb,
					"create-1",
					randomdata.FullName(randomdata.RandomGender),
					"clan-name",
					playerPublicID,
					"{}",
				)

				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Player was not found with id: %s", playerPublicID))
			})
		})

		g.Describe("Update Clan", func() {
			g.It("Should update a Clan with UpdateClan", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				metadata := "{\"x\": 1}"
				updClan, err := UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsTrue()
				g.Assert(updClan.ID).Equal(clan.ID)

				dbClan, err := GetClanByPublicID(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.Metadata).Equal(metadata)
			})

			g.It("Should not update a Clan if player is not the clan owner with UpdateClan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				player := PlayerFactory.MustCreate().(*Player)
				err = testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				metadata := "{\"x\": 1}"
				_, err = UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsFalse()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Clan was not found with id: %s", clan.PublicID))
			})

			g.It("Should not update a Clan with Invalid Data with UpdateClan", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				metadata := "it will not work because i am not a json"
				_, err = UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsFalse()
				g.Assert(err.Error()).Equal("pq: invalid input syntax for type json")
			})
		})

		g.Describe("Get List of Clans", func() {
			g.It("Should get all clans", func() {
				player, _, err := GetTestClans(testDb, "", "", 10)
				g.Assert(err == nil).IsTrue()

				clans, err := GetAllClans(testDb, player.GameID)
				g.Assert(err == nil).IsTrue()
				g.Assert(len(clans)).Equal(10)
			})

			g.It("Should fail when game id is empty", func() {
				clans, err := GetAllClans(testDb, "")
				g.Assert(clans == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Game ID is required to retrieve Clan!")
			})

			g.It("Should fail when connection fails", func() {
				clans, err := GetAllClans(faultyDb, "game-id")
				g.Assert(clans == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("pq: role \"khan_tet\" does not exist")
			})
		})

		g.Describe("Get Clan Details", func() {
			g.It("Should get clan members", func() {
				clan, _, players, _, err := GetClanWithMemberships(
					testDb, 10, "clan-details", "clan-details-clan",
				)
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(10)

				for i := 0; i < 10; i++ {
					g.Assert(members[i]["playerName"]).Equal(players[i].Name)
				}
			})

			g.It("Should not get deleted clan members", func() {
				clan, _, players, memberships, err := GetClanWithMemberships(
					testDb, 10, "more-clan-details", "more-clan-details-clan",
				)
				g.Assert(err == nil).IsTrue()

				memberships[9].DeletedAt = time.Now().UnixNano()
				memberships[9].DeletedBy = clan.OwnerID
				_, err = testDb.Update(memberships[9])
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(9)

				for i := 0; i < 9; i++ {
					g.Assert(members[i]["playerName"]).Equal(players[i].Name)
				}
			})

			g.It("Should get clan details even if no members", func() {
				clan, _, _, _, err := GetClanWithMemberships(
					testDb, 0, "clan-details-2", "clan-details-2-clan",
				)
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(0)
			})

			g.It("Should fail if clan does not exist", func() {
				clanData, err := GetClanDetails(testDb, "fake-game-id", "fake-public-id")
				g.Assert(clanData == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: fake-public-id")
			})

		})

		g.Describe("Clan Search", func() {
			g.It("Should return clan by search term", func() {
				player, _, err := GetTestClans(
					testDb, "", "clan-search-clan", 10,
				)
				g.Assert(err == nil).IsTrue()

				clans, err := SearchClan(testDb, player.GameID, "SEARCH")
				fmt.Println(err)
				g.Assert(err == nil).IsTrue()

				g.Assert(len(clans)).Equal(10)
			})
		})
	})
}
<commit_msg>removing println<commit_after>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package models

import (
	"fmt"
	"sort"
	"testing"
	"time"

	"github.com/Pallinder/go-randomdata"
	. "github.com/franela/goblin"
)

func TestClanModel(t *testing.T) {
	g := Goblin(t)
	testDb, err := GetTestDB()
	g.Assert(err == nil).IsTrue()
	faultyDb := GetFaultyTestDB()

	g.Describe("Clan Model", func() {
		g.Describe("Basic Operations", func() {
			g.It("Should sort clans by name", func() {
				_, clans, err := GetTestClans(testDb, "test", "test-sort-clan", 10)
				g.Assert(err == nil).IsTrue()

				sort.Sort(ClanByName(clans))

				for i := 0; i < 10; i++ {
					g.Assert(clans[i].Name).Equal(fmt.Sprintf("test-sort-clan-%d", i))
				}
			})

			g.It("Should create a new Clan", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				clan := &Clan{
					GameID:   "test",
					PublicID: "test-clan-2",
					Name:     "clan-name",
					Metadata: "{}",
					OwnerID:  player.ID,
				}
				err = testDb.Insert(clan)
				g.Assert(err == nil).IsTrue()
				g.Assert(clan.ID != 0).IsTrue()

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
			})

			g.It("Should update a Clan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dt := clan.UpdatedAt

				clan.Metadata = "{ \"x\": 1 }"
				count, err := testDb.Update(clan)
				g.Assert(err == nil).IsTrue()
				g.Assert(int(count)).Equal(1)
				g.Assert(clan.UpdatedAt > dt).IsTrue()
			})
		})

		g.Describe("Get By Id", func() {
			g.It("Should get existing Clan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
			})

			g.It("Should not get non-existing Clan", func() {
				_, err = GetClanByID(testDb, -1)
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: -1")
			})
		})

		g.Describe("Get By Public Id", func() {
			g.It("Should get an existing Clan by Game and PublicID", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByPublicID(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
			})

			g.It("Should not get a non-existing Clan by Game and PublicID", func() {
				_, err = GetClanByPublicID(testDb, "invalid-game", "invalid-clan")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: invalid-clan")
			})
		})

		g.Describe("Get By Public Id and OwnerPublicID", func() {
			g.It("Should get an existing Clan by Game, PublicID and OwnerPublicID", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				dbClan, err := GetClanByPublicIDAndOwnerPublicID(testDb, clan.GameID, clan.PublicID, player.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(dbClan.ID).Equal(clan.ID)
				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
				g.Assert(dbClan.Name).Equal(clan.Name)
				g.Assert(dbClan.OwnerID).Equal(clan.OwnerID)
			})

			g.It("Should not get a non-existing Clan by Game, PublicID and OwnerPublicID", func() {
				_, err = GetClanByPublicIDAndOwnerPublicID(testDb, "invalid-game", "invalid-clan", "invalid-owner-public-id")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: invalid-clan")
			})

			g.It("Should not get a existing Clan by Game, PublicID and OwnerPublicID if not Clan owner", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				_, err = GetClanByPublicIDAndOwnerPublicID(testDb, clan.GameID, clan.PublicID, "invalid-owner-public-id")
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Clan was not found with id: %s", clan.PublicID))
			})
		})

		g.Describe("Create Clan", func() {
			g.It("Should create a new Clan with CreateClan", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				clan, err := CreateClan(
					testDb,
					player.GameID,
					"create-1",
					randomdata.FullName(randomdata.RandomGender),
					player.PublicID,
					"{}",
				)

				g.Assert(err == nil).IsTrue()
				g.Assert(clan.ID != 0).IsTrue()

				dbClan, err := GetClanByID(testDb, clan.ID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.GameID).Equal(clan.GameID)
				g.Assert(dbClan.PublicID).Equal(clan.PublicID)
			})

			g.It("Should not create a new Clan with CreateClan if invalid data", func() {
				player := PlayerFactory.MustCreate().(*Player)
				err := testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				_, err = CreateClan(
					testDb,
					player.GameID,
					randomdata.FullName(randomdata.RandomGender),
					"clan-name",
					player.PublicID,
					"it-will-fail-because-metadata-is-not-a-json",
				)

				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("pq: invalid input syntax for type json")
			})

			g.It("Should not create a new Clan with CreateClan if unexistent player", func() {
				playerPublicID := randomdata.FullName(randomdata.RandomGender)
				_, err = CreateClan(
					testDb,
					"create-1",
					randomdata.FullName(randomdata.RandomGender),
					"clan-name",
					playerPublicID,
					"{}",
				)

				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Player was not found with id: %s", playerPublicID))
			})
		})

		g.Describe("Update Clan", func() {
			g.It("Should update a Clan with UpdateClan", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				metadata := "{\"x\": 1}"
				updClan, err := UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsTrue()
				g.Assert(updClan.ID).Equal(clan.ID)

				dbClan, err := GetClanByPublicID(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()

				g.Assert(dbClan.Metadata).Equal(metadata)
			})

			g.It("Should not update a Clan if player is not the clan owner with UpdateClan", func() {
				_, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				player := PlayerFactory.MustCreate().(*Player)
				err = testDb.Insert(player)
				g.Assert(err == nil).IsTrue()

				metadata := "{\"x\": 1}"
				_, err = UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsFalse()
				g.Assert(err.Error()).Equal(fmt.Sprintf("Clan was not found with id: %s", clan.PublicID))
			})

			g.It("Should not update a Clan with Invalid Data with UpdateClan", func() {
				player, clans, err := GetTestClans(testDb, "", "", 1)
				g.Assert(err == nil).IsTrue()
				clan := clans[0]

				metadata := "it will not work because i am not a json"
				_, err = UpdateClan(
					testDb,
					clan.GameID,
					clan.PublicID,
					clan.Name,
					player.PublicID,
					metadata,
				)

				g.Assert(err == nil).IsFalse()
				g.Assert(err.Error()).Equal("pq: invalid input syntax for type json")
			})
		})

		g.Describe("Get List of Clans", func() {
			g.It("Should get all clans", func() {
				player, _, err := GetTestClans(testDb, "", "", 10)
				g.Assert(err == nil).IsTrue()

				clans, err := GetAllClans(testDb, player.GameID)
				g.Assert(err == nil).IsTrue()
				g.Assert(len(clans)).Equal(10)
			})

			g.It("Should fail when game id is empty", func() {
				clans, err := GetAllClans(testDb, "")
				g.Assert(clans == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Game ID is required to retrieve Clan!")
			})

			g.It("Should fail when connection fails", func() {
				clans, err := GetAllClans(faultyDb, "game-id")
				g.Assert(clans == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("pq: role \"khan_tet\" does not exist")
			})
		})

		g.Describe("Get Clan Details", func() {
			g.It("Should get clan members", func() {
				clan, _, players, _, err := GetClanWithMemberships(
					testDb, 10, "clan-details", "clan-details-clan",
				)
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(10)

				for i := 0; i < 10; i++ {
					g.Assert(members[i]["playerName"]).Equal(players[i].Name)
				}
			})

			g.It("Should not get deleted clan members", func() {
				clan, _, players, memberships, err := GetClanWithMemberships(
					testDb, 10, "more-clan-details", "more-clan-details-clan",
				)
				g.Assert(err == nil).IsTrue()

				memberships[9].DeletedAt = time.Now().UnixNano()
				memberships[9].DeletedBy = clan.OwnerID
				_, err = testDb.Update(memberships[9])
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(9)

				for i := 0; i < 9; i++ {
					g.Assert(members[i]["playerName"]).Equal(players[i].Name)
				}
			})

			g.It("Should get clan details even if no members", func() {
				clan, _, _, _, err := GetClanWithMemberships(
					testDb, 0, "clan-details-2", "clan-details-2-clan",
				)
				g.Assert(err == nil).IsTrue()

				clanData, err := GetClanDetails(testDb, clan.GameID, clan.PublicID)
				g.Assert(err == nil).IsTrue()
				g.Assert(clanData["name"]).Equal(clan.Name)
				g.Assert(clanData["metadata"]).Equal(clan.Metadata)
				members := clanData["members"].([]map[string]interface{})
				g.Assert(len(members)).Equal(0)
			})

			g.It("Should fail if clan does not exist", func() {
				clanData, err := GetClanDetails(testDb, "fake-game-id", "fake-public-id")
				g.Assert(clanData == nil).IsTrue()
				g.Assert(err != nil).IsTrue()
				g.Assert(err.Error()).Equal("Clan was not found with id: fake-public-id")
			})

		})

		g.Describe("Clan Search", func() {
			g.It("Should return clan by search term", func() {
				player, _, err := GetTestClans(
					testDb, "", "clan-search-clan", 10,
				)
				g.Assert(err == nil).IsTrue()

				clans, err := SearchClan(testDb, player.GameID, "SEARCH")
				g.Assert(err == nil).IsTrue()

				g.Assert(len(clans)).Equal(10)
			})
		})
	})
}
<|endoftext|>


<commit_before>// Package consumer implements command line crawl consumer from nsq.
package consumer

import (
	"errors"
	"fmt"
	"os"
	"os/signal"
	"time"

	"github.com/codegangsta/cli"
	"github.com/golang/glog"

	"github.com/crackcomm/crawl"
	"github.com/crackcomm/crawl/nsq/nsqcrawl"
)

// Spider - Spider registrator.
type Spider func(crawl.Crawler)

// App - Consumer command line application structure.
type App struct {
	// Ctx - Cli context, set on action.
	Ctx *cli.Context

	// Queue - NSQ queue. Constructed on first Action() call.
	*nsqcrawl.Queue

	// before - Flag requirements checking.
	before func(c *App) error

	// crawler - Accessed using Crawler() which constructs it on first call
	// using parameters from command line.
	crawler crawl.Crawler

	// crawlerConstructor - Constructs a crawler. Called only once in Crawler().
	// It can be changed using WithCrawlerConstructor()
	crawlerConstructor func(*App) crawl.Crawler

	// opts - Options which are applied on Action() call with all required
	// parameters from the command line context.
	opts []Option

	// spiderConstructors - List of functions which use flags to construct spider.
	spiderConstructors []func(*App) Spider
}

// Flags - Consumer app flags.
var Flags = []cli.Flag{
	cli.StringFlag{
		Name:   "topic",
		EnvVar: "TOPIC",
		Usage:  "crawl requests nsq topic (required)",
		Value:  "crawl_requests",
	},
	cli.StringFlag{
		Name:   "channel",
		EnvVar: "CHANNEL",
		Usage:  "crawl requests nsq channel (required)",
		Value:  "default",
	},
	cli.StringSliceFlag{
		Name:   "nsq-addr",
		EnvVar: "NSQ_ADDR",
	},
	cli.StringSliceFlag{
		Name:   "nsqlookup-addr",
		EnvVar: "NSQLOOKUP_ADDR",
	},
	cli.IntFlag{
		Name:   "concurrency",
		Value:  100,
		EnvVar: "CONCURRENCY",
	},
	cli.IntFlag{
		Name:   "timeout",
		Usage:  "default timeout in seconds",
		Value:  30,
		EnvVar: "TIMEOUT",
	},
}

// New - Creates nsq consumer app.
func New(opts ...Option) *cli.App {
	app := &App{opts: opts}
	cliapp := cli.NewApp()
	cliapp.Name = "crawler"
	cliapp.HelpName = cliapp.Name
	cliapp.Version = "0.0.1"
	cliapp.Usage = "nsq crawl consumer"
	cliapp.Flags = Flags
	cliapp.Action = app.Action
	return cliapp
}

// Action - Command line action.
func (app *App) Action(c *cli.Context) {
	app.Ctx = c
	app.Queue = nsqcrawl.NewQueue(c.String("topic"), c.String("channel"), c.Int("concurrency"))

	for _, opt := range app.opts {
		opt(app)
	}

	if err := app.Before(c); err != nil {
		glog.Fatal(err)
	}

	crawler := app.Crawler()

	if err := app.connectNSQ(c); err != nil {
		glog.Fatal(err)
	}

	for _, spiderConstructor := range app.spiderConstructors {
		if spider := spiderConstructor(app); spider != nil {
			spider(crawler)
		}
	}

	go func() {
		for err := range crawler.Errors() {
			glog.Warningf("crawl %v", err)
		}
	}()

	done := make(chan bool, 1)
	go func() {
		crawler.Start()
		done <- true
	}()

	glog.Infof("Started crawler (topic=%q)", c.String("topic"))

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)

	for {
		select {
		case <-done:
			glog.Info("Crawler closed")
			return
		case s := <-sig:
			glog.Infof("Received signal %v, closing crawler", s)
			if err := app.Queue.Close(); err != nil {
				glog.Fatalf("Error closing queue: %v", err)
			}
			return
		}
	}
}

func (app *App) connectNSQ(c *cli.Context) (err error) {
	nsqAddr := c.StringSlice("nsq-addr")[0]
	if err := app.Queue.Producer.Connect(nsqAddr); err != nil {
		return fmt.Errorf("Error connecting producer to %q: %v", nsqAddr, err)
	}

	if addrs := c.StringSlice("nsq-addr"); len(addrs) != 0 {
		for _, addr := range addrs {
			glog.V(3).Infof("Connecting to nsq %s", addr)
			if err := app.Queue.Consumer.Connect(addr); err != nil {
				return fmt.Errorf("Error connecting to nsq %q: %v", addr, err)
			}
			glog.V(3).Infof("Connected to nsq %s", addr)
		}
	}

	if addrs := c.StringSlice("nsqlookup-addr"); len(addrs) != 0 {
		for _, addr := range addrs {
			glog.V(3).Infof("Connecting to nsq lookup %s", addr)
			if err := app.Queue.Consumer.ConnectLookupd(addr); err != nil {
				return fmt.Errorf("Error connecting to nsq lookup %q: %v", addr, err)
			}
			glog.V(3).Infof("Connected to nsq lookup %s", addr)
		}
	}

	return
}

// Before - Executed before action.
func (app *App) Before(c *cli.Context) (err error) {
	if app.before != nil {
		err = app.before(app)
		if err != nil {
			return
		}
	}
	return beforeApp(c)
}

func beforeApp(c *cli.Context) error {
	if c.String("topic") == "" {
		return errors.New("flag --topic cannot be empty")
	}
	if len(c.StringSlice("nsq-addr")) == 0 && len(c.StringSlice("nsqlookup-addr")) == 0 {
		return errors.New("At least one --nsq-addr or --nsqlookup-addr is required")
	}
	return nil
}

// Crawler - Returns app crawler. Constructs if empty.
func (app *App) Crawler() crawl.Crawler {
	if app.crawler == nil {
		if app.crawlerConstructor != nil {
			app.crawler = app.crawlerConstructor(app)
		} else {
			app.crawler = crawlerConstructor(app)
		}
	}
	return app.crawler
}

func crawlerConstructor(app *App) crawl.Crawler {
	return crawl.New(
		crawl.WithQueue(app.Queue),
		crawl.WithConcurrency(app.Ctx.Int("concurrency")),
		crawl.WithDefaultTimeout(time.Duration(app.Ctx.Int("timeout"))*time.Second),
	)
}
<commit_msg>nsq consumer: small refactor<commit_after>// Package consumer implements command line crawl consumer from nsq.
package consumer

import (
	"os"
	"os/signal"
	"time"

	"github.com/golang/glog"
	"gopkg.in/urfave/cli.v2"

	clinsq "github.com/crackcomm/cli-nsq"
	"github.com/crackcomm/crawl"
	"github.com/crackcomm/crawl/nsq/nsqcrawl"
)

// Spider - Spider registrator.
type Spider func(crawl.Crawler)

// App - Consumer command line application structure.
type App struct {
	// Ctx - Cli context, set on action.
	Ctx *cli.Context

	// Queue - NSQ queue. Constructed on first Action() call.
	*nsqcrawl.Queue

	// before - Flag requirements checking.
	before func(c *App) error

	// crawler - Accessed using Crawler() which constructs it on first call
	// using parameters from command line.
	crawler crawl.Crawler

	// crawlerConstructor - Constructs a crawler. Called only once in Crawler().
	// It can be changed using WithCrawlerConstructor()
	crawlerConstructor func(*App) crawl.Crawler

	// opts - Options which are applied on Action() call with all required
	// parameters from the command line context.
	opts []Option

	// spiderConstructors - List of functions which use flags to construct spider.
	spiderConstructors []func(*App) Spider
}

// Flags - Consumer app flags.
var Flags = []cli.Flag{
	clinsq.AddrFlag,
	clinsq.LookupAddrFlag,
	clinsq.TopicFlag,
	clinsq.ChannelFlag,
	&cli.IntFlag{
		Name:    "concurrency",
		Value:   100,
		EnvVars: []string{"CONCURRENCY"},
	},
	&cli.IntFlag{
		Name:    "timeout",
		Usage:   "default timeout in seconds",
		Value:   30,
		EnvVars: []string{"TIMEOUT"},
	},
}

// New - Creates nsq consumer app.
func New(opts ...Option) *cli.App {
	app := &App{opts: opts}
	cliapp := (&cli.App{})
	cliapp.Name = "crawler"
	cliapp.HelpName = cliapp.Name
	cliapp.Version = "0.0.1"
	cliapp.Usage = "nsq crawl consumer"
	cliapp.Flags = Flags
	cliapp.Action = app.Action
	return cliapp
}

// Action - Command line action.
func (app *App) Action(c *cli.Context) error {
	app.Ctx = c
	app.Queue = nsqcrawl.NewQueue(c.String("topic"), c.String("channel"), c.Int("concurrency"))

	for _, opt := range app.opts {
		opt(app)
	}

	if err := app.Before(c); err != nil {
		return err
	}

	// Construct crawler
	// It has to be done before connecting to nsq
	crawler := app.Crawler()

	// Register all spiders
	for _, spiderConstructor := range app.spiderConstructors {
		if spider := spiderConstructor(app); spider != nil {
			spider(crawler)
		}
	}

	// Connect to nsq and nsqlookup
	if err := clinsq.Connect(c); err != nil {
		return err
	}

	go func() {
		for err := range crawler.Errors() {
			glog.Warningf("crawl %v", err)
		}
	}()

	done := make(chan bool, 1)
	go func() {
		crawler.Start()
		done <- true
	}()

	glog.Infof("Started crawler (topic=%q)", c.String("topic"))

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)

	for {
		select {
		case <-done:
			glog.Info("Crawler closed")
			return nil
		case s := <-sig:
			glog.Infof("Received signal %v, closing crawler", s)
			return app.Queue.Close()
		}
	}
}

// Before - Executed before action.
func (app *App) Before(c *cli.Context) (err error) {
	if app.before != nil {
		err = app.before(app)
		if err != nil {
			return
		}
	}
	return clinsq.RequireAll(c)
}

// Crawler - Returns app crawler. Constructs if empty.
func (app *App) Crawler() crawl.Crawler {
	if app.crawler == nil {
		if app.crawlerConstructor != nil {
			app.crawler = app.crawlerConstructor(app)
		} else {
			app.crawler = crawlerConstructor(app)
		}
	}
	return app.crawler
}

func crawlerConstructor(app *App) crawl.Crawler {
	return crawl.New(
		crawl.WithQueue(app.Queue),
		crawl.WithConcurrency(app.Ctx.Int("concurrency")),
		crawl.WithDefaultTimeout(time.Duration(app.Ctx.Int("timeout"))*time.Second),
	)
}
<|endoftext|>


<commit_before>package common

import (
	"errors"

	"github.com/RichardKnop/machinery/v1/brokers/iface"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/log"
	"github.com/RichardKnop/machinery/v1/retry"
	"github.com/RichardKnop/machinery/v1/tasks"
)

// Broker represents a base broker structure
type Broker struct {
	cnf                 *config.Config
	registeredTaskNames []string
	retry               bool
	retryFunc           func(chan int)
	retryStopChan       chan int
	stopChan            chan int
}

// NewBroker creates new Broker instance
func NewBroker(cnf *config.Config) Broker {
	return Broker{cnf: cnf, retry: true}
}

// GetConfig returns config
func (b *Broker) GetConfig() *config.Config {
	return b.cnf
}

// GetRetry ...
func (b *Broker) GetRetry() bool {
	return b.retry
}

// GetRetryFunc ...
func (b *Broker) GetRetryFunc() func(chan int) {
	return b.retryFunc
}

// GetRetryStopChan ...
func (b *Broker) GetRetryStopChan() chan int {
	return b.retryStopChan
}

// GetStopChan ...
func (b *Broker) GetStopChan() chan int {
	return b.stopChan
}

// Publish places a new message on the default queue
func (b *Broker) Publish(signature *tasks.Signature) error {
	return errors.New("Not implemented")
}

// SetRegisteredTaskNames sets registered task names
func (b *Broker) SetRegisteredTaskNames(names []string) {
	b.registeredTaskNames = names
}

// IsTaskRegistered returns true if the task is registered with this broker
func (b *Broker) IsTaskRegistered(name string) bool {
	for _, registeredTaskName := range b.registeredTaskNames {
		if registeredTaskName == name {
			return true
		}
	}
	return false
}

// GetPendingTasks returns a slice of task.Signatures waiting in the queue
func (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {
	return nil, errors.New("Not implemented")
}

// StartConsuming is a common part of StartConsuming method
func (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {
	if b.retryFunc == nil {
		b.retryFunc = retry.Closure()
	}

	b.stopChan = make(chan int)
	b.retryStopChan = make(chan int)
}

// StopConsuming is a common part of StopConsuming
func (b *Broker) StopConsuming() {
	// Do not retry from now on
	b.retry = false
	// Stop the retry closure earlier
	select {
	case b.retryStopChan <- 1:
		log.WARNING.Print("Stopping retry closure.")
	default:
	}
	// Notifying the stop channel stops consuming of messages
	select {
	case b.stopChan <- 1:
		log.WARNING.Print("Stop channel")
	default:
	}
}

// GetRegisteredTaskNames returns registered tasks names
func (b *Broker) GetRegisteredTaskNames() []string {
	return b.registeredTaskNames
}

// AdjustRoutingKey makes sure the routing key is correct.
// If the routing key is an empty string:
// a) set it to binding key for direct exchange type
// b) set it to default queue name
func (b *Broker) AdjustRoutingKey(s *tasks.Signature) {
	if s.RoutingKey != "" {
		return
	}

	s.RoutingKey = b.GetConfig().DefaultQueue
}
<commit_msg>for Redis broker: suggestion to simplify shutdown logic - closing broker.stopChan<commit_after>package common

import (
	"errors"

	"github.com/RichardKnop/machinery/v1/brokers/iface"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/log"
	"github.com/RichardKnop/machinery/v1/retry"
	"github.com/RichardKnop/machinery/v1/tasks"
)

// Broker represents a base broker structure
type Broker struct {
	cnf                 *config.Config
	registeredTaskNames []string
	retry               bool
	retryFunc           func(chan int)
	retryStopChan       chan int
	stopChan            chan int
}

// NewBroker creates new Broker instance
func NewBroker(cnf *config.Config) Broker {
	return Broker{cnf: cnf, retry: true}
}

// GetConfig returns config
func (b *Broker) GetConfig() *config.Config {
	return b.cnf
}

// GetRetry ...
func (b *Broker) GetRetry() bool {
	return b.retry
}

// GetRetryFunc ...
func (b *Broker) GetRetryFunc() func(chan int) {
	return b.retryFunc
}

// GetRetryStopChan ...
func (b *Broker) GetRetryStopChan() chan int {
	return b.retryStopChan
}

// GetStopChan ...
func (b *Broker) GetStopChan() chan int {
	return b.stopChan
}

// Publish places a new message on the default queue
func (b *Broker) Publish(signature *tasks.Signature) error {
	return errors.New("Not implemented")
}

// SetRegisteredTaskNames sets registered task names
func (b *Broker) SetRegisteredTaskNames(names []string) {
	b.registeredTaskNames = names
}

// IsTaskRegistered returns true if the task is registered with this broker
func (b *Broker) IsTaskRegistered(name string) bool {
	for _, registeredTaskName := range b.registeredTaskNames {
		if registeredTaskName == name {
			return true
		}
	}
	return false
}

// GetPendingTasks returns a slice of task.Signatures waiting in the queue
func (b *Broker) GetPendingTasks(queue string) ([]*tasks.Signature, error) {
	return nil, errors.New("Not implemented")
}

// StartConsuming is a common part of StartConsuming method
func (b *Broker) StartConsuming(consumerTag string, concurrency int, taskProcessor iface.TaskProcessor) {
	if b.retryFunc == nil {
		b.retryFunc = retry.Closure()
	}

	b.stopChan = make(chan int)
	b.retryStopChan = make(chan int)
}

// StopConsuming is a common part of StopConsuming
func (b *Broker) StopConsuming() {
	// Do not retry from now on
	b.retry = false
	// Stop the retry closure earlier
	select {
	case b.retryStopChan <- 1:
		log.WARNING.Print("Stopping retry closure.")
	default:
	}
	// Notifying the stop channel stops consuming of messages
	close(b.stopChan)
	log.WARNING.Print("Stop channel")
}

// GetRegisteredTaskNames returns registered tasks names
func (b *Broker) GetRegisteredTaskNames() []string {
	return b.registeredTaskNames
}

// AdjustRoutingKey makes sure the routing key is correct.
// If the routing key is an empty string:
// a) set it to binding key for direct exchange type
// b) set it to default queue name
func (b *Broker) AdjustRoutingKey(s *tasks.Signature) {
	if s.RoutingKey != "" {
		return
	}

	s.RoutingKey = b.GetConfig().DefaultQueue
}
<|endoftext|>


<commit_before>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 openchain

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
	"golang.org/x/net/context"

	"github.com/openblockchain/obc-peer/openchain/chaincode"
	"github.com/openblockchain/obc-peer/openchain/container"
	"github.com/openblockchain/obc-peer/openchain/crypto"
	"github.com/openblockchain/obc-peer/openchain/peer"
	"github.com/openblockchain/obc-peer/openchain/util"
	pb "github.com/openblockchain/obc-peer/protos"
)

var devopsLogger = logging.MustGetLogger("devops")

// NewDevopsServer creates and returns a new Devops server instance.
func NewDevopsServer(coord peer.MessageHandlerCoordinator) *Devops {
	d := new(Devops)
	d.coord = coord
	return d
}

// Devops implementation of Devops services
type Devops struct {
	coord peer.MessageHandlerCoordinator
}

// Login establishes the security context with the Devops service
func (d *Devops) Login(ctx context.Context, secret *pb.Secret) (*pb.Response, error) {
	if err := crypto.RegisterClient(secret.EnrollId, nil, secret.EnrollId, secret.EnrollSecret); nil != err {
		return &pb.Response{Status: pb.Response_FAILURE, Msg: []byte(err.Error())}, nil
	}
	return &pb.Response{Status: pb.Response_SUCCESS}, nil

	// TODO: Handle timeout and expiration
}

// Build builds the supplied chaincode image
func (*Devops) Build(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	mode := viper.GetString("chaincode.mode")
	var codePackageBytes []byte
	if mode != chaincode.DevModeUserRunsChaincode {
		devopsLogger.Debug("Received build request for chaincode spec: %v", spec)
		if err := CheckSpec(spec); err != nil {
			return nil, err
		}
		// Get new VM and as for building of container image
		vm, err := container.NewVM()
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
		// Build the spec
		codePackageBytes, err = vm.BuildChaincodeContainer(spec)
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
	}
	chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
	return chaincodeDeploymentSpec, nil
}

// Deploy deploys the supplied chaincode image to the validators through a transaction
func (d *Devops) Deploy(ctx context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	// First build and get the deployment spec
	chaincodeDeploymentSpec, err := d.Build(ctx, spec)

	if err != nil {
		devopsLogger.Error(fmt.Sprintf("Error deploying chaincode spec: %v\n\n error: %s", spec, err))
		return nil, err
	}
	//devopsLogger.Debug("returning status: %s", status)
	// Now create the Transactions message and send to Peer.
	uuid, uuidErr := util.GenerateUUID()
	if uuidErr != nil {
		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
		return nil, uuidErr
	}

	var tx *pb.Transaction
	var sec crypto.Client

	if viper.GetBool("security.enabled") {
		devopsLogger.Debug("Creating secure deployment transaction %s using secure context %s", uuid, spec.SecureContext)
		sec, err = crypto.InitClient(spec.SecureContext, nil)
		defer crypto.CloseClient(sec)

		// remove the security context since we are no longer need it down stream
		spec.SecureContext = ""

		if nil != err {
			return nil, err
		}

		tx, err = sec.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
		if nil != err {
			return nil, err
		}
	} else {
		devopsLogger.Debug("Creating deployment transaction (%s)", uuid)
		tx, err = pb.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
		if err != nil {
			return nil, fmt.Errorf("Error deploying chaincode: %s ", err)
		}
	}

	devopsLogger.Debug("Sending deploy transaction (%s) to validator", tx.Uuid)
	resp := d.coord.ExecuteTransaction(tx)
	if resp.Status == pb.Response_FAILURE {
		err = fmt.Errorf(string(resp.Msg))
	}

	return chaincodeDeploymentSpec, err
}

func (d *Devops) invokeOrQuery(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec, invoke bool) (*pb.Response, error) {

	// Now create the Transactions message and send to Peer.
	uuid, uuidErr := util.GenerateUUID()
	if uuidErr != nil {
		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
		return nil, uuidErr
	}
	var transaction *pb.Transaction
	var err error
	transaction, err = d.createExecTx(chaincodeInvocationSpec, uuid, invoke)
	if err != nil {
		return nil, err
	}

	devopsLogger.Debug("Sending invocation transaction (%s) to validator", transaction.Uuid)
	resp := d.coord.ExecuteTransaction(transaction)
	if resp.Status == pb.Response_FAILURE {
		err = fmt.Errorf(string(resp.Msg))
	}

	return resp, err
}

func (d *Devops) createExecTx(spec *pb.ChaincodeInvocationSpec, uuid string, invokeTx bool) (*pb.Transaction, error) {
	var tx *pb.Transaction
	var err error
	if invokeTx {
		if viper.GetBool("security.enabled") {
			devopsLogger.Debug("Creating secure invocation transaction %s using secure context %s", uuid, spec.ChaincodeSpec.SecureContext)
			sec, err := crypto.InitClient(spec.ChaincodeSpec.SecureContext, nil)
			defer crypto.CloseClient(sec)

			// remove the security context since we are no longer need it down stream
			spec.ChaincodeSpec.SecureContext = ""

			if nil != err {
				return nil, err
			}
			tx, err = sec.NewChaincodeExecute(spec, uuid)
			if nil != err {
				return nil, err
			}
		} else {
			devopsLogger.Debug("Creating invocation transaction (%s)", uuid)
			tx, err = pb.NewChaincodeExecute(spec, uuid, pb.Transaction_CHAINCODE_EXECUTE)
			if nil != err {
				return nil, err
			}
		}
	} else {
		devopsLogger.Debug("Creating query transaction (%s)", uuid)
		tx, err = pb.NewChaincodeExecute(spec, uuid, pb.Transaction_CHAINCODE_QUERY)
		if nil != err {
			return nil, err
		}
	}
	return tx, nil
}

// Invoke performs the supplied invocation on the specified chaincode through a transaction
func (d *Devops) Invoke(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec) (*pb.Response, error) {
	return d.invokeOrQuery(ctx, chaincodeInvocationSpec, true)
}

// Query performs the supplied query on the specified chaincode through a transaction
func (d *Devops) Query(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec) (*pb.Response, error) {
	return d.invokeOrQuery(ctx, chaincodeInvocationSpec, false)
}

// CheckSpec to see if chaincode resides within current package capture for language.
func CheckSpec(spec *pb.ChaincodeSpec) error {
	// Don't allow nil value
	if spec == nil {
		return errors.New("Expected chaincode specification, nil received")
	}

	// Only allow GOLANG type at the moment
	if spec.Type != pb.ChaincodeSpec_GOLANG {
		return fmt.Errorf("Only support '%s' currently", pb.ChaincodeSpec_GOLANG)
	}
	if err := checkGolangSpec(spec); err != nil {
		return err
	}
	devopsLogger.Debug("Validated spec:  %v", spec)

	// Check the version
	_, err := semver.Make(spec.ChaincodeID.Version)
	return err
}

func checkGolangSpec(spec *pb.ChaincodeSpec) error {
	pathToCheck := filepath.Join(os.Getenv("GOPATH"), "src", spec.ChaincodeID.Url)
	exists, err := pathExists(pathToCheck)
	if err != nil {
		return fmt.Errorf("Error validating chaincode path: %s", err)
	}
	if !exists {
		return fmt.Errorf("Path to chaincode does not exist: %s", spec.ChaincodeID.Url)
	}
	return nil
}

// Returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

//BuildLocal builds a given chaincode code
func BuildLocal(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	devopsLogger.Debug("Received build request for chaincode spec: %v", spec)
	mode := viper.GetString("chaincode.mode")
	var codePackageBytes []byte
	if mode != chaincode.DevModeUserRunsChaincode {
		if err := CheckSpec(spec); err != nil {
			devopsLogger.Debug("check spec failed: %s", err)
			return nil, err
		}
		// Get new VM and as for building of container image
		vm, err := container.NewVM()
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
		// Build the spec
		codePackageBytes, err = vm.BuildChaincodeContainer(spec)
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
	}
	chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
	return chaincodeDeploymentSpec, nil
}

// DeployLocal deploys the supplied chaincode image to the local peer
func DeployLocal(ctx context.Context, spec *pb.ChaincodeSpec) ([]byte, error) {
	// First build and get the deployment spec
	chaincodeDeploymentSpec, err := BuildLocal(ctx, spec)

	if err != nil {
		devopsLogger.Error(fmt.Sprintf("Error deploying chaincode spec: %v\n\n error: %s", spec, err))
		return nil, err
	}
	//devopsLogger.Debug("returning status: %s", status)
	// Now create the Transactions message and send to Peer.
	uuid, uuidErr := util.GenerateUUID()
	if uuidErr != nil {
		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
		return nil, uuidErr
	}
	transaction, err := pb.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
	if err != nil {
		return nil, fmt.Errorf("Error deploying chaincode: %s ", err)
	}
	return chaincode.Execute(ctx, chaincode.GetChain(chaincode.DefaultChain), transaction)
}
<commit_msg>comment out DeployLocal function as we no longer need<commit_after>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 openchain

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
	"golang.org/x/net/context"

	"github.com/openblockchain/obc-peer/openchain/chaincode"
	"github.com/openblockchain/obc-peer/openchain/container"
	"github.com/openblockchain/obc-peer/openchain/crypto"
	"github.com/openblockchain/obc-peer/openchain/peer"
	"github.com/openblockchain/obc-peer/openchain/util"
	pb "github.com/openblockchain/obc-peer/protos"
)

var devopsLogger = logging.MustGetLogger("devops")

// NewDevopsServer creates and returns a new Devops server instance.
func NewDevopsServer(coord peer.MessageHandlerCoordinator) *Devops {
	d := new(Devops)
	d.coord = coord
	return d
}

// Devops implementation of Devops services
type Devops struct {
	coord peer.MessageHandlerCoordinator
}

// Login establishes the security context with the Devops service
func (d *Devops) Login(ctx context.Context, secret *pb.Secret) (*pb.Response, error) {
	if err := crypto.RegisterClient(secret.EnrollId, nil, secret.EnrollId, secret.EnrollSecret); nil != err {
		return &pb.Response{Status: pb.Response_FAILURE, Msg: []byte(err.Error())}, nil
	}
	return &pb.Response{Status: pb.Response_SUCCESS}, nil

	// TODO: Handle timeout and expiration
}

// Build builds the supplied chaincode image
func (*Devops) Build(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	mode := viper.GetString("chaincode.mode")
	var codePackageBytes []byte
	if mode != chaincode.DevModeUserRunsChaincode {
		devopsLogger.Debug("Received build request for chaincode spec: %v", spec)
		if err := CheckSpec(spec); err != nil {
			return nil, err
		}
		// Get new VM and as for building of container image
		vm, err := container.NewVM()
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
		// Build the spec
		codePackageBytes, err = vm.BuildChaincodeContainer(spec)
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
	}
	chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
	return chaincodeDeploymentSpec, nil
}

// Deploy deploys the supplied chaincode image to the validators through a transaction
func (d *Devops) Deploy(ctx context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	// First build and get the deployment spec
	chaincodeDeploymentSpec, err := d.Build(ctx, spec)

	if err != nil {
		devopsLogger.Error(fmt.Sprintf("Error deploying chaincode spec: %v\n\n error: %s", spec, err))
		return nil, err
	}
	//devopsLogger.Debug("returning status: %s", status)
	// Now create the Transactions message and send to Peer.
	uuid, uuidErr := util.GenerateUUID()
	if uuidErr != nil {
		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
		return nil, uuidErr
	}

	var tx *pb.Transaction
	var sec crypto.Client

	if viper.GetBool("security.enabled") {
		devopsLogger.Debug("Creating secure deployment transaction %s using secure context %s", uuid, spec.SecureContext)
		sec, err = crypto.InitClient(spec.SecureContext, nil)
		defer crypto.CloseClient(sec)

		// remove the security context since we are no longer need it down stream
		spec.SecureContext = ""

		if nil != err {
			return nil, err
		}

		tx, err = sec.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
		if nil != err {
			return nil, err
		}
	} else {
		devopsLogger.Debug("Creating deployment transaction (%s)", uuid)
		tx, err = pb.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
		if err != nil {
			return nil, fmt.Errorf("Error deploying chaincode: %s ", err)
		}
	}

	devopsLogger.Debug("Sending deploy transaction (%s) to validator", tx.Uuid)
	resp := d.coord.ExecuteTransaction(tx)
	if resp.Status == pb.Response_FAILURE {
		err = fmt.Errorf(string(resp.Msg))
	}

	return chaincodeDeploymentSpec, err
}

func (d *Devops) invokeOrQuery(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec, invoke bool) (*pb.Response, error) {

	// Now create the Transactions message and send to Peer.
	uuid, uuidErr := util.GenerateUUID()
	if uuidErr != nil {
		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
		return nil, uuidErr
	}
	var transaction *pb.Transaction
	var err error
	transaction, err = d.createExecTx(chaincodeInvocationSpec, uuid, invoke)
	if err != nil {
		return nil, err
	}

	devopsLogger.Debug("Sending invocation transaction (%s) to validator", transaction.Uuid)
	resp := d.coord.ExecuteTransaction(transaction)
	if resp.Status == pb.Response_FAILURE {
		err = fmt.Errorf(string(resp.Msg))
	}

	return resp, err
}

func (d *Devops) createExecTx(spec *pb.ChaincodeInvocationSpec, uuid string, invokeTx bool) (*pb.Transaction, error) {
	var tx *pb.Transaction
	var err error
	if invokeTx {
		if viper.GetBool("security.enabled") {
			devopsLogger.Debug("Creating secure invocation transaction %s using secure context %s", uuid, spec.ChaincodeSpec.SecureContext)
			sec, err := crypto.InitClient(spec.ChaincodeSpec.SecureContext, nil)
			defer crypto.CloseClient(sec)

			// remove the security context since we are no longer need it down stream
			spec.ChaincodeSpec.SecureContext = ""

			if nil != err {
				return nil, err
			}
			tx, err = sec.NewChaincodeExecute(spec, uuid)
			if nil != err {
				return nil, err
			}
		} else {
			devopsLogger.Debug("Creating invocation transaction (%s)", uuid)
			tx, err = pb.NewChaincodeExecute(spec, uuid, pb.Transaction_CHAINCODE_EXECUTE)
			if nil != err {
				return nil, err
			}
		}
	} else {
		devopsLogger.Debug("Creating query transaction (%s)", uuid)
		tx, err = pb.NewChaincodeExecute(spec, uuid, pb.Transaction_CHAINCODE_QUERY)
		if nil != err {
			return nil, err
		}
	}
	return tx, nil
}

// Invoke performs the supplied invocation on the specified chaincode through a transaction
func (d *Devops) Invoke(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec) (*pb.Response, error) {
	return d.invokeOrQuery(ctx, chaincodeInvocationSpec, true)
}

// Query performs the supplied query on the specified chaincode through a transaction
func (d *Devops) Query(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec) (*pb.Response, error) {
	return d.invokeOrQuery(ctx, chaincodeInvocationSpec, false)
}

// CheckSpec to see if chaincode resides within current package capture for language.
func CheckSpec(spec *pb.ChaincodeSpec) error {
	// Don't allow nil value
	if spec == nil {
		return errors.New("Expected chaincode specification, nil received")
	}

	// Only allow GOLANG type at the moment
	if spec.Type != pb.ChaincodeSpec_GOLANG {
		return fmt.Errorf("Only support '%s' currently", pb.ChaincodeSpec_GOLANG)
	}
	if err := checkGolangSpec(spec); err != nil {
		return err
	}
	devopsLogger.Debug("Validated spec:  %v", spec)

	// Check the version
	_, err := semver.Make(spec.ChaincodeID.Version)
	return err
}

func checkGolangSpec(spec *pb.ChaincodeSpec) error {
	pathToCheck := filepath.Join(os.Getenv("GOPATH"), "src", spec.ChaincodeID.Url)
	exists, err := pathExists(pathToCheck)
	if err != nil {
		return fmt.Errorf("Error validating chaincode path: %s", err)
	}
	if !exists {
		return fmt.Errorf("Path to chaincode does not exist: %s", spec.ChaincodeID.Url)
	}
	return nil
}

// Returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

//BuildLocal builds a given chaincode code
func BuildLocal(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
	devopsLogger.Debug("Received build request for chaincode spec: %v", spec)
	mode := viper.GetString("chaincode.mode")
	var codePackageBytes []byte
	if mode != chaincode.DevModeUserRunsChaincode {
		if err := CheckSpec(spec); err != nil {
			devopsLogger.Debug("check spec failed: %s", err)
			return nil, err
		}
		// Get new VM and as for building of container image
		vm, err := container.NewVM()
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
		// Build the spec
		codePackageBytes, err = vm.BuildChaincodeContainer(spec)
		if err != nil {
			devopsLogger.Error(fmt.Sprintf("Error getting VM: %s", err))
			return nil, err
		}
	}
	chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
	return chaincodeDeploymentSpec, nil
}

// DeployLocal deploys the supplied chaincode image to the local peer
// func DeployLocal(ctx context.Context, spec *pb.ChaincodeSpec) ([]byte, error) {
// 	// First build and get the deployment spec
// 	chaincodeDeploymentSpec, err := BuildLocal(ctx, spec)
//
// 	if err != nil {
// 		devopsLogger.Error(fmt.Sprintf("Error deploying chaincode spec: %v\n\n error: %s", spec, err))
// 		return nil, err
// 	}
// 	//devopsLogger.Debug("returning status: %s", status)
// 	// Now create the Transactions message and send to Peer.
// 	uuid, uuidErr := util.GenerateUUID()
// 	if uuidErr != nil {
// 		devopsLogger.Error(fmt.Sprintf("Error generating UUID: %s", uuidErr))
// 		return nil, uuidErr
// 	}
// 	transaction, err := pb.NewChaincodeDeployTransaction(chaincodeDeploymentSpec, uuid)
// 	if err != nil {
// 		return nil, fmt.Errorf("Error deploying chaincode: %s ", err)
// 	}
// 	return chaincode.Execute(ctx, chaincode.GetChain(chaincode.DefaultChain), transaction)
// }
<|endoftext|>


<commit_before>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package outputs

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	textTemplate "text/template"

	"github.com/dustin/go-humanize"
	"github.com/martinlindhe/unit"

	"github.com/soumya92/barista/bar"
)

var defaultFuncs = make(map[string]interface{})

// AddTemplateFunc adds template functions available to all templates.
func AddTemplateFunc(name string, f interface{}) {
	defaultFuncs[name] = f
}

// TextTemplate creates a TemplateFunc from the given text template.
func TextTemplate(tpl string) TemplateFunc {
	t := textTemplate.Must(
		textTemplate.New("text").
			Funcs(textTemplate.FuncMap(defaultFuncs)).
			Parse(tpl))
	return func(arg interface{}) bar.Output {
		var out bytes.Buffer
		if err := t.Execute(&out, arg); err != nil {
			return Error(err)
		}
		return bar.TextSegment(out.String())
	}
}

// PangoTemplate creates a TemplateFunc from the given pango template.
// It uses go's html/template to escape input properly.
func PangoTemplate(tpl string) TemplateFunc {
	t := htmlTemplate.Must(
		htmlTemplate.New("pango").
			Funcs(htmlTemplate.FuncMap(defaultFuncs)).
			Parse(tpl))
	return func(arg interface{}) bar.Output {
		var out bytes.Buffer
		if err := t.Execute(&out, arg); err != nil {
			return Error(err)
		}
		return bar.PangoSegment(out.String())
	}
}

func Bytesize(v unit.Datasize) string {
	intval := uint64(v.Bytes())
	return humanize.Bytes(intval)
}
func IBytesize(v unit.Datasize) string {
	intval := uint64(v.Bytes())
	return humanize.IBytes(intval)
}
func Byterate(v unit.Datarate) string {
	intval := uint64(v.BytesPerSecond())
	return fmt.Sprintf("%s/s", humanize.Bytes(intval))
}
func IByterate(v unit.Datarate) string {
	intval := uint64(v.BytesPerSecond())
	return fmt.Sprintf("%s/s", humanize.IBytes(intval))
}

// init adds some useful default template functions.
func init() {
	AddTemplateFunc("bytesize", Bytesize)
	AddTemplateFunc("ibytesize", IBytesize)
	AddTemplateFunc("byterate", Byterate)
	AddTemplateFunc("ibyterate", IByterate)
}
<commit_msg>Add doc comments to exported datasize/rate formatting functions<commit_after>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package outputs

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	textTemplate "text/template"

	"github.com/dustin/go-humanize"
	"github.com/martinlindhe/unit"

	"github.com/soumya92/barista/bar"
)

var defaultFuncs = make(map[string]interface{})

// AddTemplateFunc adds template functions available to all templates.
func AddTemplateFunc(name string, f interface{}) {
	defaultFuncs[name] = f
}

// TextTemplate creates a TemplateFunc from the given text template.
func TextTemplate(tpl string) TemplateFunc {
	t := textTemplate.Must(
		textTemplate.New("text").
			Funcs(textTemplate.FuncMap(defaultFuncs)).
			Parse(tpl))
	return func(arg interface{}) bar.Output {
		var out bytes.Buffer
		if err := t.Execute(&out, arg); err != nil {
			return Error(err)
		}
		return bar.TextSegment(out.String())
	}
}

// PangoTemplate creates a TemplateFunc from the given pango template.
// It uses go's html/template to escape input properly.
func PangoTemplate(tpl string) TemplateFunc {
	t := htmlTemplate.Must(
		htmlTemplate.New("pango").
			Funcs(htmlTemplate.FuncMap(defaultFuncs)).
			Parse(tpl))
	return func(arg interface{}) bar.Output {
		var out bytes.Buffer
		if err := t.Execute(&out, arg); err != nil {
			return Error(err)
		}
		return bar.PangoSegment(out.String())
	}
}

// Bytesize formats a Datasize in SI units using go-humanize.
// e.g. Bytesize(10 * unit.Megabyte) == "10 MB"
func Bytesize(v unit.Datasize) string {
	intval := uint64(v.Bytes())
	return humanize.Bytes(intval)
}

// IBytesize formats a Datasize in IEC units using go-humanize.
// e.g. IBytesize(10 * unit.Mebibyte) == "10 MiB"
func IBytesize(v unit.Datasize) string {
	intval := uint64(v.Bytes())
	return humanize.IBytes(intval)
}

// Byterate formats a Datarate in SI units using go-humanize.
// e.g. Byterate(10 * unit.MegabytePerSecond) == "10 MB/s"
func Byterate(v unit.Datarate) string {
	intval := uint64(v.BytesPerSecond())
	return fmt.Sprintf("%s/s", humanize.Bytes(intval))
}

// IByterate formats a Datarate in IEC units using go-humanize.
// e.g. Byterate(10 * unit.MebibytePerSecond) == "10 MiB/s"
func IByterate(v unit.Datarate) string {
	intval := uint64(v.BytesPerSecond())
	return fmt.Sprintf("%s/s", humanize.IBytes(intval))
}

// init adds some useful default template functions.
func init() {
	AddTemplateFunc("bytesize", Bytesize)
	AddTemplateFunc("ibytesize", IBytesize)
	AddTemplateFunc("byterate", Byterate)
	AddTemplateFunc("ibyterate", IByterate)
}
<|endoftext|>


<commit_before>package packet

import (
	"container/list"
	"fmt"
	"time"

	"github.com/chzyer/flow"
)

type HeartBeatStatInfo struct {
	total  time.Duration
	count  int64
	droped int64
}

func (s *HeartBeatStatInfo) dropStr() string {
	return fmt.Sprintf("%v/%v", s.droped, s.count)
}

func (s *HeartBeatStatInfo) rtt() time.Duration {
	if s.count == 0 {
		return 0
	}
	d := s.total / time.Duration(s.count)
	if d > time.Millisecond {
		d = d / time.Millisecond * time.Millisecond
	} else if d > time.Microsecond {
		d = d / time.Microsecond * time.Microsecond
	}
	return d
}

func (s *HeartBeatStatInfo) Add(s2 *HeartBeatStatInfo) {
	s.total += s2.total
	s.count += s2.count
	s.droped += s2.droped
}

type HeartBeatStat struct {
	start    time.Time
	lastTime int
	slots    [30]HeartBeatStatInfo // 15 mintue, 30s one second
	size     int
}

func (s *HeartBeatStat) getMin(n int) *HeartBeatStatInfo {
	h := &HeartBeatStatInfo{}
	n *= 2
	if n > s.size {
		n = s.size
	}
	for i := 0; i < n; i++ {
		h.Add(&s.slots[i])
	}
	return h
}

func (s *HeartBeatStat) getSlot() *HeartBeatStatInfo {
	now := time.Now()
	ts := now.Minute() * 2
	if now.Second() > 30 {
		ts += 1
	}

	if ts != s.lastTime {
		s.lastTime = ts
		for i := s.size - 1; i >= 1; i-- {
			s.slots[i] = s.slots[i-1]
		}
		s.slots[0] = HeartBeatStatInfo{}

		if s.size < len(s.slots) {
			s.size++
		}
	}
	if s.size == 0 {
		s.size = 1
	}
	return &s.slots[0]
}

func (s *HeartBeatStat) needClean() error {
	stat := s.getMin(1)
	if stat.count > 10 {
		if stat.droped >= stat.count/2 {
			return fmt.Errorf("droped packets more than a half (%v>%v/2)", stat.droped, stat.count)
		}
	} else if stat.count > 5 && stat.droped == stat.count {
		return fmt.Errorf("too much droped packets")
	}
	min2 := s.getMin(2)
	min5 := s.getMin(5)
	if min2.rtt() > min5.rtt()*2 && min2.rtt() > 200*time.Millisecond {
		return fmt.Errorf("rtt in 2min(%v) more than 2 times of 5mins(%v)",
			min2.rtt(), min5.rtt(),
		)
	}
	return nil
}

func (s *HeartBeatStat) submitDrop(n int) {
	slot := s.getSlot()
	slot.droped += int64(n)
	slot.count++
}

func (s *HeartBeatStat) submitDuration(d time.Duration) {
	slot := s.getSlot()

	slot.total += d
	slot.count++
}

func (s *HeartBeatStat) lifeTime() time.Duration {
	return time.Now().Round(time.Second).Sub(s.start.Round(time.Second))
}

func (s HeartBeatStat) String() string {
	min1 := s.getMin(1)
	min5 := s.getMin(5)
	min15 := s.getMin(15)
	return fmt.Sprintf("avg: %v %v %v, drop: %v %v %v",
		min15.rtt(), min5.rtt(), min1.rtt(),
		min15.dropStr(), min5.dropStr(), min1.dropStr(),
	)
}

type heartBeatItem struct {
	reqid uint32
	time  time.Time
}

type HeartBeatStage struct {
	flow        *flow.Flow
	staging     *list.List
	receiveChan chan *IV
	addChan     chan *IV
	timeout     time.Duration
	clean       func(error)
	name        string

	stat HeartBeatStat
}

func NewHeartBeatStage(f *flow.Flow, timeout time.Duration, name string, clean func(error)) *HeartBeatStage {
	hbs := &HeartBeatStage{
		timeout:     timeout,
		staging:     list.New(),
		receiveChan: make(chan *IV, 8),
		addChan:     make(chan *IV, 8),
		name:        name,
		flow:        f,
		clean:       clean,
	}
	hbs.stat.start = time.Now()
	go hbs.loop()
	return hbs
}

func (h *HeartBeatStage) New() *Packet {
	return New(nil, HEARTBEAT)
}

func (h *HeartBeatStage) Add(iv *IV) {
	h.addChan <- iv
}

func (h *HeartBeatStage) Receive(iv *IV) {
	h.receiveChan <- iv
}

func (h *HeartBeatStage) findElem(reqid uint32) *list.Element {
	now := time.Now()
	for elem := h.staging.Front(); elem != nil; elem = elem.Next() {
		if elem.Value.(heartBeatItem).reqid == reqid {
			return elem
		}

		if now.Sub(elem.Value.(heartBeatItem).time) > h.timeout {
			h.stat.submitDrop(1)
			h.staging.Remove(elem)
		}
	}
	return nil
}

func (h *HeartBeatStage) GetStat() *HeartBeatStat {
	s := h.stat
	return &s
}

func (h *HeartBeatStage) tryClean() bool {
	if err := h.stat.needClean(); err != nil {
		h.clean(err)
		return true
	}
	return false
}

func (h *HeartBeatStage) item(elem *list.Element) heartBeatItem {
	return elem.Value.(heartBeatItem)
}

func (h *HeartBeatStage) loop() {
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()
loop:
	for {
		select {
		case <-h.flow.IsClose():
			break loop
		case <-ticker.C:
			h.findElem(0) // just clean up
		case iv := <-h.receiveChan:
			elem := h.findElem(iv.ReqId)
			if elem == nil {
				// stat
				continue
			}
			h.stat.submitDuration(time.Now().Sub(h.item(elem).time))
			h.staging.Remove(elem)
		case iv := <-h.addChan:
			h.staging.PushBack(heartBeatItem{iv.ReqId, time.Now()})
		}
		if h.tryClean() {
			break loop
		}
	}
}
<commit_msg>[heartbeat] use 10second per segment<commit_after>package packet

import (
	"container/list"
	"fmt"
	"time"

	"github.com/chzyer/flow"
)

type HeartBeatStatInfo struct {
	total  time.Duration
	count  int64
	droped int64
}

func (s *HeartBeatStatInfo) dropStr() string {
	return fmt.Sprintf("%v/%v", s.droped, s.count)
}

func (s *HeartBeatStatInfo) rtt() time.Duration {
	if s.count == 0 {
		return 0
	}
	d := s.total / time.Duration(s.count)
	if d > time.Millisecond {
		d = d / time.Millisecond * time.Millisecond
	} else if d > time.Microsecond {
		d = d / time.Microsecond * time.Microsecond
	}
	return d
}

func (s *HeartBeatStatInfo) Add(s2 *HeartBeatStatInfo) {
	s.total += s2.total
	s.count += s2.count
	s.droped += s2.droped
}

type HeartBeatStat struct {
	start    time.Time
	lastTime int
	slots    [90]HeartBeatStatInfo // 15 mintue, 10s one item
	size     int
}

func (s *HeartBeatStat) getMin(n int) *HeartBeatStatInfo {
	h := &HeartBeatStatInfo{}
	n *= 6
	if n > s.size {
		n = s.size
	}
	for i := 0; i < n; i++ {
		h.Add(&s.slots[i])
	}
	return h
}

func (s *HeartBeatStat) getSlot() *HeartBeatStatInfo {
	now := time.Now()
	ts := (now.Minute() * 6) + (now.Second() / 10)

	if ts != s.lastTime {
		s.lastTime = ts
		for i := s.size - 1; i >= 1; i-- {
			s.slots[i] = s.slots[i-1]
		}
		s.slots[0] = HeartBeatStatInfo{}

		if s.size < len(s.slots) {
			s.size++
		}
	}
	if s.size == 0 {
		s.size = 1
	}
	return &s.slots[0]
}

func (s *HeartBeatStat) needClean() error {
	stat := s.getMin(1)
	if stat.count > 10 {
		if stat.droped >= stat.count/2 {
			return fmt.Errorf("droped packets more than a half (%v>%v/2)", stat.droped, stat.count)
		}
	} else if stat.count > 5 && stat.droped == stat.count {
		return fmt.Errorf("too much droped packets")
	}
	min2 := s.getMin(2)
	min5 := s.getMin(5)
	if min2.rtt() > min5.rtt()*2 && min2.rtt() > 200*time.Millisecond {
		return fmt.Errorf("rtt in 2min(%v) more than 2 times of 5mins(%v)",
			min2.rtt(), min5.rtt(),
		)
	}
	return nil
}

func (s *HeartBeatStat) submitDrop(n int) {
	slot := s.getSlot()
	slot.droped += int64(n)
	slot.count++
}

func (s *HeartBeatStat) submitDuration(d time.Duration) {
	slot := s.getSlot()

	slot.total += d
	slot.count++
}

func (s *HeartBeatStat) lifeTime() time.Duration {
	return time.Now().Round(time.Second).Sub(s.start.Round(time.Second))
}

func (s HeartBeatStat) String() string {
	min1 := s.getMin(1)
	min5 := s.getMin(5)
	min15 := s.getMin(15)
	return fmt.Sprintf("avg: %v %v %v, drop: %v %v %v",
		min15.rtt(), min5.rtt(), min1.rtt(),
		min15.dropStr(), min5.dropStr(), min1.dropStr(),
	)
}

type heartBeatItem struct {
	reqid uint32
	time  time.Time
}

type HeartBeatStage struct {
	flow        *flow.Flow
	staging     *list.List
	receiveChan chan *IV
	addChan     chan *IV
	timeout     time.Duration
	clean       func(error)
	name        string

	stat HeartBeatStat
}

func NewHeartBeatStage(f *flow.Flow, timeout time.Duration, name string, clean func(error)) *HeartBeatStage {
	hbs := &HeartBeatStage{
		timeout:     timeout,
		staging:     list.New(),
		receiveChan: make(chan *IV, 8),
		addChan:     make(chan *IV, 8),
		name:        name,
		flow:        f,
		clean:       clean,
	}
	hbs.stat.start = time.Now()
	go hbs.loop()
	return hbs
}

func (h *HeartBeatStage) New() *Packet {
	return New(nil, HEARTBEAT)
}

func (h *HeartBeatStage) Add(iv *IV) {
	h.addChan <- iv
}

func (h *HeartBeatStage) Receive(iv *IV) {
	h.receiveChan <- iv
}

func (h *HeartBeatStage) findElem(reqid uint32) *list.Element {
	now := time.Now()
	for elem := h.staging.Front(); elem != nil; elem = elem.Next() {
		if elem.Value.(heartBeatItem).reqid == reqid {
			return elem
		}

		if now.Sub(elem.Value.(heartBeatItem).time) > h.timeout {
			h.stat.submitDrop(1)
			h.staging.Remove(elem)
		}
	}
	return nil
}

func (h *HeartBeatStage) GetStat() *HeartBeatStat {
	s := h.stat
	return &s
}

func (h *HeartBeatStage) tryClean() bool {
	if err := h.stat.needClean(); err != nil {
		h.clean(err)
		return true
	}
	return false
}

func (h *HeartBeatStage) item(elem *list.Element) heartBeatItem {
	return elem.Value.(heartBeatItem)
}

func (h *HeartBeatStage) loop() {
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()
loop:
	for {
		select {
		case <-h.flow.IsClose():
			break loop
		case <-ticker.C:
			h.findElem(0) // just clean up
		case iv := <-h.receiveChan:
			elem := h.findElem(iv.ReqId)
			if elem == nil {
				// stat
				continue
			}
			h.stat.submitDuration(time.Now().Sub(h.item(elem).time))
			h.staging.Remove(elem)
		case iv := <-h.addChan:
			h.staging.PushBack(heartBeatItem{iv.ReqId, time.Now()})
		}
		if h.tryClean() {
			break loop
		}
	}
}
<|endoftext|>


<commit_before>package cerebrum

import (
	"net"
	"time"

	"github.com/hashicorp/raft"
	"github.com/hashicorp/serf/serf"
)

// const (
// 	// SerfCheckID           = "serfHealth"
// 	// SerfCheckName         = "Serf Health Status"
// 	// SerfCheckAliveOutput  = "Agent alive and reachable"
// 	// SerfCheckFailedOutput = "Agent not live or unreachable"
// 	KappaServiceID   = "kappa"
// 	KappaServiceName = "kappa"
// 	LeaderEventName  = "kappa:new-leader"
// )

func (c *cerebrum) IsLeader() bool {
	return c.raft.State() == raft.Leader
}

func (c *cerebrum) setLeader(name string) {
	c.leader = name
}

// monitorLeadership is used to monitor if we acquire or lose our role
// as the leader in the Raft cluster. There is some work the leader is
// expected to do, so we must react to changes
func (c *cerebrum) monitorLeadership() {
	leaderCh := c.raft.LeaderCh()
	var stopCh chan struct{}
	for {
		select {
		case isLeader := <-leaderCh:
			if isLeader {
				stopCh = make(chan struct{})
				go c.leaderLoop(stopCh)
				c.logger.Info("cluster leadership acquired")
			} else if stopCh != nil {
				close(stopCh)
				stopCh = nil
				c.logger.Info("cluster leadership lost")
			}
		case <-c.t.Dying():
			return
		}
	}
}

// leaderLoop runs as long as we are the leader to run various
// maintenance activities
func (c *cerebrum) leaderLoop(stopCh chan struct{}) {
	// Ensure we revoke leadership on stepdown
	defer c.revokeLeadership()

	// Fire a user event indicating a new leader
	payload := []byte(c.config.NodeName)
	if err := c.serf.UserEvent(CerebrumEventPrefix+":new-leader", payload, false); err != nil {
		c.logger.Warn("failed to broadcast new leader event: %v", err)
	}

	// Reconcile channel is only used once initial reconcile
	// has succeeded
	var reconcileCh chan serf.Member
	establishedLeader := false

RECONCILE:
	// Setup a reconciliation timer
	reconcileCh = nil
	interval := time.After(c.config.ReconcileInterval)

	// Apply a raft barrier to ensure our FSM is caught up
	barrier := c.raft.Barrier(0)
	if err := barrier.Error(); err != nil {
		c.logger.Error("failed to wait for barrier", err)
		goto WAIT
	}

	// Check if we need to handle initial leadership actions
	if !establishedLeader {
		if err := c.establishLeadership(); err != nil {
			c.logger.Error("failed to establish leadership", err)
			goto WAIT
		}
		establishedLeader = true
	}

	// Initial reconcile worked, now we can process the channel
	// updates
	reconcileCh = c.reconcileCh

WAIT:
	// Periodically reconcile as long as we are the leader,
	// or when Serf events arrive
	for {
		select {
		case <-stopCh:
			return
		case <-c.t.Dying():
			return
		case <-interval:
			goto RECONCILE
		case member := <-reconcileCh:
			c.reconcileMember(member)
		}
	}
}

// establishLeadership is invoked once we become leader and are able
// to invoke an initial barrier. The barrier is used to ensure any
// previously inflight transactions have been committed and that our
// state is up-to-date.
func (c *cerebrum) establishLeadership() error {
	if c.config.EstablishLeadership != nil {
		return c.config.EstablishLeadership()
	}
	return nil
}

// revokeLeadership is invoked once we step down as leader.
// This is used to cleanup any state that may be specific to a leader.
func (c *cerebrum) revokeLeadership() error {
	if c.config.RevokeLeadership != nil {
		return c.config.RevokeLeadership()
	}
	return nil
}

// reconcileMember is used to do an async reconcile of a single
// serf member
func (c *cerebrum) reconcileMember(member serf.Member) (err error) {

	// Check if this is a member we should handle
	var node *NodeDetails
	if node, err = GetNodeDetails(member); err != nil {
		return err
	}

	// Validate node is in the same data center
	if node.DataCenter != c.config.DataCenter {
		c.logger.Warn("skipping reconcile of node", "node", member)
		return nil
	}

	switch member.Status {
	case serf.StatusAlive:
		err = c.handleAliveMember(member, node)
	case serf.StatusFailed:
		err = c.handleFailedMember(member, node)
	case serf.StatusLeft:
		err = c.handleLeftMember(member, node)
	case StatusReap:
		err = c.handleReapMember(member, node)
	}
	if err != nil {
		c.logger.Error("failed to reconcile member", "member", member, "err", err)
		return err
	}
	return nil
}

// handleAliveMember is used to ensure the node
// is registered, with a passing health check.
func (c *cerebrum) handleAliveMember(member serf.Member, details *NodeDetails) error {

	// Attempt to join the consul server
	if err := c.joinConsulServer(member, details); err != nil {
		return err
	}

	c.logger.Info("member joined, marking health alive", "member", member.Name)
	return c.updateNodeStatus(details, StatusAlive)
}

// handleFailedMember is used to mark the node's status
// as being critical, along with all checks as unknown.
func (c *cerebrum) handleFailedMember(member serf.Member, details *NodeDetails) error {
	c.logger.Info("member failed, marking health critical", "member", member.Name)
	return c.updateNodeStatus(details, StatusFailed)
}

// handleLeftMember is used to handle members that gracefully
// left. They are deregistered if necessary.
func (c *cerebrum) handleLeftMember(member serf.Member, details *NodeDetails) error {
	return c.handleDeregisterMember(StatusLeft, member, details)
}

// handleReapMember is used to handle members that have been
// reaped after a prolonged failure. They are deregistered.
func (c *cerebrum) handleReapMember(member serf.Member, details *NodeDetails) error {
	return c.handleDeregisterMember(StatusReaped, member, details)
}

// handleDeregisterMember is used to deregister a member of a given reason
func (c *cerebrum) handleDeregisterMember(reason NodeStatus, member serf.Member, details *NodeDetails) error {
	// Do not deregister ourself. This can only happen if the current leader
	// is leaving. Instead, we should allow a follower to take-over and
	// deregister us later.
	if member.Name == c.config.NodeName {
		c.logger.Warn("deregistering self should be done by follower", "node", c.config.NodeName)
		return nil
	}

	// Remove from Raft peers if this was a server
	if err := c.removeConsulServer(member, details.Port); err != nil {
		return err
	}

	// Deregister the node
	c.logger.Info("deregistering member", "name", member.Name, "reason", reason)
	return c.updateNodeStatus(details, reason)
}

// joinConsulServer is used to try to join another consul server
func (c *cerebrum) joinConsulServer(m serf.Member, details *NodeDetails) error {
	// Do not join ourself
	if m.Name == c.config.NodeName {
		return nil
	}

	// Check for possibility of multiple bootstrap nodes
	if details.Bootstrap {
		members := c.serf.Members()
		for _, member := range members {
			det, err := GetNodeDetails(member)
			if err != nil && member.Name != m.Name && det.Bootstrap {
				c.logger.Error("Two nodes are both in bootstrap mode. Only one"+
					" node should be in bootstrap mode, not adding Raft peer.",
					"node-1", m.Name, "node-2", member.Name)
				return nil
			}
		}
	}

	// Attempt to add as a peer
	var addr net.Addr = &net.TCPAddr{IP: m.Addr, Port: details.Port}
	future := c.raft.AddPeer(addr.String())
	if err := future.Error(); err != nil && err != raft.ErrKnownPeer {
		c.logger.Error("failed to add raft peer", "err", err)
		return err
	}
	return nil
}

// removeConsulServer is used to try to remove a consul server that has left
func (c *cerebrum) removeConsulServer(m serf.Member, port int) error {
	// Attempt to remove as peer
	peer := &net.TCPAddr{IP: m.Addr, Port: port}
	future := c.raft.RemovePeer(peer.String())
	if err := future.Error(); err != nil && err != raft.ErrUnknownPeer {
		c.logger.Error("failed to remove raft peer",
			"peer", peer, "err", err)
		return err
	} else if err == nil {
		c.logger.Info("removed server as peer", "name", m.Name)
	}
	return nil
}
<commit_msg>Update leader processing to use context instead of tomb<commit_after>package cerebrum

import (
	"net"
	"time"

	"github.com/hashicorp/raft"
	"github.com/hashicorp/serf/serf"
)

// const (
// 	// SerfCheckID           = "serfHealth"
// 	// SerfCheckName         = "Serf Health Status"
// 	// SerfCheckAliveOutput  = "Agent alive and reachable"
// 	// SerfCheckFailedOutput = "Agent not live or unreachable"
// 	KappaServiceID   = "kappa"
// 	KappaServiceName = "kappa"
// 	LeaderEventName  = "kappa:new-leader"
// )

func (c *cerebrum) IsLeader() bool {
	return c.raft.State() == raft.Leader
}

func (c *cerebrum) setLeader(name string) {
	c.leader = name
}

// monitorLeadership is used to monitor if we acquire or lose our role
// as the leader in the Raft cluster. There is some work the leader is
// expected to do, so we must react to changes
func (c *cerebrum) monitorLeadership() {
	leaderCh := c.raft.LeaderCh()
	var stopCh chan struct{}
	for {
		select {
		case isLeader := <-leaderCh:
			if isLeader {
				stopCh = make(chan struct{})
				go c.leaderLoop(stopCh)
				c.logger.Info("cluster leadership acquired")
			} else if stopCh != nil {
				close(stopCh)
				stopCh = nil
				c.logger.Info("cluster leadership lost")
			}
		case <-c.context.Done():
			return
		}
	}
}

// leaderLoop runs as long as we are the leader to run various
// maintenance activities
func (c *cerebrum) leaderLoop(stopCh chan struct{}) {
	// Ensure we revoke leadership on stepdown
	defer c.revokeLeadership()

	// Fire a user event indicating a new leader
	payload := []byte(c.config.NodeName)
	if err := c.serf.UserEvent(CerebrumEventPrefix+":new-leader", payload, false); err != nil {
		c.logger.Warn("failed to broadcast new leader event: %v", err)
	}

	// Reconcile channel is only used once initial reconcile
	// has succeeded
	var reconcileCh chan serf.Member
	establishedLeader := false

RECONCILE:
	// Setup a reconciliation timer
	reconcileCh = nil
	interval := time.After(c.config.ReconcileInterval)

	// Apply a raft barrier to ensure our FSM is caught up
	barrier := c.raft.Barrier(0)
	if err := barrier.Error(); err != nil {
		c.logger.Error("failed to wait for barrier", err)
		goto WAIT
	}

	// Check if we need to handle initial leadership actions
	if !establishedLeader {
		if err := c.establishLeadership(); err != nil {
			c.logger.Error("failed to establish leadership", err)
			goto WAIT
		}
		establishedLeader = true
	}

	// Initial reconcile worked, now we can process the channel
	// updates
	reconcileCh = c.reconcileCh

WAIT:
	// Periodically reconcile as long as we are the leader,
	// or when Serf events arrive
	for {
		select {
		case <-stopCh:
			return
		case <-c.context.Done():
			return
		case <-interval:
			goto RECONCILE
		case member := <-reconcileCh:
			c.reconcileMember(member)
		}
	}
}

// establishLeadership is invoked once we become leader and are able
// to invoke an initial barrier. The barrier is used to ensure any
// previously inflight transactions have been committed and that our
// state is up-to-date.
func (c *cerebrum) establishLeadership() error {
	if c.config.EstablishLeadership != nil {
		return c.config.EstablishLeadership()
	}
	return nil
}

// revokeLeadership is invoked once we step down as leader.
// This is used to cleanup any state that may be specific to a leader.
func (c *cerebrum) revokeLeadership() error {
	if c.config.RevokeLeadership != nil {
		return c.config.RevokeLeadership()
	}
	return nil
}

// reconcileMember is used to do an async reconcile of a single
// serf member
func (c *cerebrum) reconcileMember(member serf.Member) (err error) {

	// Check if this is a member we should handle
	var node *NodeDetails
	if node, err = GetNodeDetails(member); err != nil {
		return err
	}

	// Validate node is in the same data center
	if node.DataCenter != c.config.DataCenter {
		c.logger.Warn("skipping reconcile of node", "node", member)
		return nil
	}

	switch member.Status {
	case serf.StatusAlive:
		err = c.handleAliveMember(member, node)
	case serf.StatusFailed:
		err = c.handleFailedMember(member, node)
	case serf.StatusLeft:
		err = c.handleLeftMember(member, node)
	case StatusReap:
		err = c.handleReapMember(member, node)
	}
	if err != nil {
		c.logger.Error("failed to reconcile member", "member", member, "err", err)
		return err
	}
	return nil
}

// handleAliveMember is used to ensure the node
// is registered, with a passing health check.
func (c *cerebrum) handleAliveMember(member serf.Member, details *NodeDetails) error {

	// Attempt to join the consul server
	if err := c.joinConsulServer(member, details); err != nil {
		return err
	}

	c.logger.Info("member joined, marking health alive", "member", member.Name)
	return c.updateNodeStatus(details, StatusAlive)
}

// handleFailedMember is used to mark the node's status
// as being critical, along with all checks as unknown.
func (c *cerebrum) handleFailedMember(member serf.Member, details *NodeDetails) error {
	c.logger.Info("member failed, marking health critical", "member", member.Name)
	return c.updateNodeStatus(details, StatusFailed)
}

// handleLeftMember is used to handle members that gracefully
// left. They are deregistered if necessary.
func (c *cerebrum) handleLeftMember(member serf.Member, details *NodeDetails) error {
	return c.handleDeregisterMember(StatusLeft, member, details)
}

// handleReapMember is used to handle members that have been
// reaped after a prolonged failure. They are deregistered.
func (c *cerebrum) handleReapMember(member serf.Member, details *NodeDetails) error {
	return c.handleDeregisterMember(StatusReaped, member, details)
}

// handleDeregisterMember is used to deregister a member of a given reason
func (c *cerebrum) handleDeregisterMember(reason NodeStatus, member serf.Member, details *NodeDetails) error {
	// Do not deregister ourself. This can only happen if the current leader
	// is leaving. Instead, we should allow a follower to take-over and
	// deregister us later.
	if member.Name == c.config.NodeName {
		c.logger.Warn("deregistering self should be done by follower", "node", c.config.NodeName)
		return nil
	}

	// Remove from Raft peers if this was a server
	if err := c.removeConsulServer(member, details.Port); err != nil {
		return err
	}

	// Deregister the node
	c.logger.Info("deregistering member", "name", member.Name, "reason", reason)
	return c.updateNodeStatus(details, reason)
}

// joinConsulServer is used to try to join another consul server
func (c *cerebrum) joinConsulServer(m serf.Member, details *NodeDetails) error {
	// Do not join ourself
	if m.Name == c.config.NodeName {
		return nil
	}

	// Check for possibility of multiple bootstrap nodes
	if details.Bootstrap {
		members := c.serf.Members()
		for _, member := range members {
			det, err := GetNodeDetails(member)
			if err != nil && member.Name != m.Name && det.Bootstrap {
				c.logger.Error("Two nodes are both in bootstrap mode. Only one"+
					" node should be in bootstrap mode, not adding Raft peer.",
					"node-1", m.Name, "node-2", member.Name)
				return nil
			}
		}
	}

	// Attempt to add as a peer
	var addr net.Addr = &net.TCPAddr{IP: m.Addr, Port: details.Port}
	future := c.raft.AddPeer(addr.String())
	if err := future.Error(); err != nil && err != raft.ErrKnownPeer {
		c.logger.Error("failed to add raft peer", "err", err)
		return err
	}
	return nil
}

// removeConsulServer is used to try to remove a consul server that has left
func (c *cerebrum) removeConsulServer(m serf.Member, port int) error {
	// Attempt to remove as peer
	peer := &net.TCPAddr{IP: m.Addr, Port: port}
	future := c.raft.RemovePeer(peer.String())
	if err := future.Error(); err != nil && err != raft.ErrUnknownPeer {
		c.logger.Error("failed to remove raft peer",
			"peer", peer, "err", err)
		return err
	} else if err == nil {
		c.logger.Info("removed server as peer", "name", m.Name)
	}
	return nil
}
<|endoftext|>