<commit_before>package keystoneplugin

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/iopred/bruxism"
)

type keystoneDungeonID int

const (
	BRH keystoneDungeonID = iota
	COS
	DHT
	EOA
	HOV
	MOS
	NL
	ARC
	VOW
)

type keystoneDungeon struct {
	Name    string
	Aliases []string
}

var dungeons map[keystoneDungeonID]*keystoneDungeon = map[keystoneDungeonID]*keystoneDungeon{
	BRH: &keystoneDungeon{
		Name:    "Black Rook Hold",
		Aliases: []string{"black rook hold", "brh", "black rook", "hold"},
	},
	COS: &keystoneDungeon{
		Name:    "Court of Stars",
		Aliases: []string{"court of stars", "cos", "court"},
	},
	DHT: &keystoneDungeon{
		Name:    "Darkheart Thicket",
		Aliases: []string{"darkheart thicket", "dht", "thicket", "darkheart"},
	},
	EOA: &keystoneDungeon{
		Name:    "Eye of Azshara",
		Aliases: []string{"eye of azshara", "eoa", "eye", "azshara"},
	},
	HOV: &keystoneDungeon{
		Name:    "Halls of Valor",
		Aliases: []string{"halls of valor", "hall of valor", "hov", "halls"},
	},
	MOS: &keystoneDungeon{
		Name:    "Maw of Souls",
		Aliases: []string{"maw of souls", "mos", "maw"},
	},
	NL: &keystoneDungeon{
		Name:    "Neltharion's Lair",
		Aliases: []string{"neltharion's lair", "nl", "neltharions lair", "nel", "nelth", "lair"},
	},
	ARC: &keystoneDungeon{
		Name:    "The Arcway",
		Aliases: []string{"the arcway", "arc", "arcway"},
	},
	VOW: &keystoneDungeon{
		Name:    "Vault of the Wardens",
		Aliases: []string{"vault of the wardens", "vow", "vault", "warden", "wardens"},
	},
}

type keystone struct {
	User      string
	Dungeon   keystoneDungeonID
	Level     int
	Depleted  bool
	Modifiers []string
}

func (k *keystone) String() string {
	str := fmt.Sprintf("Level %d **%s**", k.Level, dungeons[k.Dungeon].Name)
	if len(k.Modifiers) != 0 {
		str += " *(" + strings.Join(k.Modifiers, ", ") + ")*"
	}
	if k.Depleted {
		str += " - Depleted"
	}
	return str
}

type keystoneChannel struct {
	Users        map[string]*keystone
	LastModified time.Time
}

func lastTuesday(t time.Time) time.Time {
	year, month, day := t.Date()
	t = time.Date(year, month, day, 0, 0, 0, 0, t.Location())
	for t.Weekday() == 1 {
		t = t.Add(-24 * time.Hour)
	}

	return t
}

func (c *keystoneChannel) check() {
	if time.Now().After(lastTuesday(c.LastModified).Add(24 * 7 * time.Hour)) {
		c.Users = map[string]*keystone{}
	}
}

func (c *keystoneChannel) add(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, query string) bool {
	query = strings.ToLower(query)
	for dungeonID, dungeon := range dungeons {
		for _, alias := range dungeon.Aliases {
			if strings.Index(query, alias+" ") == 0 {
				query = query[len(alias)+1:]
				if len(query) == 0 {
					return false
				}

				parts := strings.Split(query, " ")

				level, err := strconv.Atoi(parts[0])
				if err != nil {
					return false
				}

				parts = parts[1:]

				depleted := false
				modifiers := []string{}

				for _, part := range parts {
					if strings.Index(part, "deplete") == 0 {
						depleted = true
					} else {
						modifiers = append(modifiers, part)
					}
				}

				c.Users[message.UserID()] = &keystone{
					User:      message.UserName(),
					Dungeon:   dungeonID,
					Level:     level,
					Depleted:  depleted,
					Modifiers: modifiers,
				}
				c.LastModified = time.Now()

				return true
			}
		}
	}
	return false
}

type keystoneList []*keystone

// Len is part of sort.Interface.
func (s keystoneList) Len() int {
	return len(s)
}

// Swap is part of sort.Interface.
func (s keystoneList) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s keystoneList) Less(i, j int) bool {
	if s[i].Level == s[j].Level {
		return dungeons[s[i].Dungeon].Name > dungeons[s[j].Dungeon].Name
	}
	return s[i].Level > s[j].Level
}

func (c *keystoneChannel) list(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if len(c.Users) == 0 {
		service.SendMessage(message.Channel(), "No keystones have been set this week.")
		return
	}

	keystones := keystoneList{}
	for _, keystone := range c.Users {
		keystones = append(keystones, keystone)
	}

	sort.Sort(keystones)

	content := ""
	for _, keystone := range keystones {
		if len(content) != 0 {
			content += "\n"
		}
		content += keystone.String()
		content += " - " + keystone.User
	}
	service.SendMessage(message.Channel(), content)
}

type keystonePlugin struct {
	sync.RWMutex
	Channels map[string]*keystoneChannel
}

// Name returns the name of the plugin.
func (p *keystonePlugin) Name() string {
	return "Keystone"
}

// Load will load plugin state from a byte array.
func (p *keystonePlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}

	if p.Channels == nil {
		p.Channels = make(map[string]*keystoneChannel)
	}

	return nil
}

// Save will save plugin state to a byte array.
func (p *keystonePlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

// Help returns a list of help strings that are printed when the user requests them.
func (p *keystonePlugin) Help(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	if p.Channels[message.Channel()] == nil {
		return nil
	}

	if !detailed {
		return []string{
			"`keystone <set <dungeon> <level> [modifiers]|list|deplete|undeplete>` - WoW Mythic Keystones.",
		}
	}

	return []string{
		"`keystone set<dungeon> <level> [modifiers]` - Set your keystone for this week.",
		"`keystone list` - Lists all this weeks keystones.",
		"`keystone deplete` - Depletes your keystone.",
		"Examples:",
		"`keystone set hov 5 teeming` - Adds a Level 5 Halls of Valor keystone with teeming.",
		"`keystone set eye of azshara 2 depleted` - Adds a depleted Level 2 Eye of Azshara keystone.",
	}
}

// Message handler.
func (p *keystonePlugin) Message(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	defer bruxism.MessageRecover()
	if !service.IsMe(message) {
		messageChannel := message.Channel()

		if service.IsBotOwner(message) && bruxism.MatchesCommand(service, "keystone", message) {
			_, parts := bruxism.ParseCommand(service, message)

			p.Lock()
			defer p.Unlock()

			switch parts[0] {
			case "start":
				p.Channels[messageChannel] = &keystoneChannel{
					Users: map[string]*keystone{},
				}
				service.SendMessage(messageChannel, "This channel is now tracking mythic keystones.")
			case "stop":
				delete(p.Channels, messageChannel)
				service.SendMessage(messageChannel, "This channel is no longer tracking mythic keystones.")
			}
		} else if channel, ok := p.Channels[messageChannel]; ok {
			channel.check()
			parts := strings.Split(strings.ToLower(message.Message()), " ")
			if len(parts) < 2 || parts[0] != "keystone" {
				return
			}

			switch parts[1] {
			case "set":
				if len(parts) > 2 && channel.add(bot, service, message, message.Message()[len("keystone set "):]) {
					channel.list(bot, service, message)
				} else {
					service.SendMessage(messageChannel, "Invalid keystone. Eg: `keystone hall of valor 3 sanguine`")
				}
			case "list":
				channel.list(bot, service, message)
			case "deplete":
				keystone := channel.Users[message.UserID()]
				if keystone == nil {
					service.SendMessage(messageChannel, "You haven't set a keystone this week.")
				} else {
					keystone.Depleted = true
					keystone.User = message.UserName()
					service.SendMessage(messageChannel, fmt.Sprintf("Keystone depleted: %s", keystone.String()))
				}
			case "undeplete":
				keystone := channel.Users[message.UserID()]
				if keystone == nil {
					service.SendMessage(messageChannel, "You haven't set a keystone this week.")
				} else {
					keystone.Depleted = false
					keystone.User = message.UserName()
					service.SendMessage(messageChannel, fmt.Sprintf("Keystone undepleted: %s", keystone.String()))
				}
			}
		}
	}
}

// Stats will return the stats for a plugin.
func (p *keystonePlugin) Stats(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) []string {
	return nil
}

// New will create a new wormhole plugin.
func New() bruxism.Plugin {
	return &keystonePlugin{}
}
<commit_msg>Fix keystone resetting.<commit_after>package keystoneplugin

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/iopred/bruxism"
)

type keystoneDungeonID int

const (
	BRH keystoneDungeonID = iota
	COS
	DHT
	EOA
	HOV
	MOS
	NL
	ARC
	VOW
)

type keystoneDungeon struct {
	Name    string
	Aliases []string
}

var dungeons map[keystoneDungeonID]*keystoneDungeon = map[keystoneDungeonID]*keystoneDungeon{
	BRH: &keystoneDungeon{
		Name:    "Black Rook Hold",
		Aliases: []string{"black rook hold", "brh", "black rook", "hold"},
	},
	COS: &keystoneDungeon{
		Name:    "Court of Stars",
		Aliases: []string{"court of stars", "cos", "court"},
	},
	DHT: &keystoneDungeon{
		Name:    "Darkheart Thicket",
		Aliases: []string{"darkheart thicket", "dht", "thicket", "darkheart"},
	},
	EOA: &keystoneDungeon{
		Name:    "Eye of Azshara",
		Aliases: []string{"eye of azshara", "eoa", "eye", "azshara"},
	},
	HOV: &keystoneDungeon{
		Name:    "Halls of Valor",
		Aliases: []string{"halls of valor", "hall of valor", "hov", "halls"},
	},
	MOS: &keystoneDungeon{
		Name:    "Maw of Souls",
		Aliases: []string{"maw of souls", "mos", "maw"},
	},
	NL: &keystoneDungeon{
		Name:    "Neltharion's Lair",
		Aliases: []string{"neltharion's lair", "nl", "neltharions lair", "nel", "nelth", "lair"},
	},
	ARC: &keystoneDungeon{
		Name:    "The Arcway",
		Aliases: []string{"the arcway", "arc", "arcway"},
	},
	VOW: &keystoneDungeon{
		Name:    "Vault of the Wardens",
		Aliases: []string{"vault of the wardens", "vow", "vault", "warden", "wardens"},
	},
}

type keystone struct {
	User      string
	Dungeon   keystoneDungeonID
	Level     int
	Depleted  bool
	Modifiers []string
}

func (k *keystone) String() string {
	str := fmt.Sprintf("Level %d **%s**", k.Level, dungeons[k.Dungeon].Name)
	if len(k.Modifiers) != 0 {
		str += " *(" + strings.Join(k.Modifiers, ", ") + ")*"
	}
	if k.Depleted {
		str += " - Depleted"
	}
	return str
}

type keystoneChannel struct {
	Users        map[string]*keystone
	LastModified time.Time
}

func lastTuesday(t time.Time) time.Time {
	year, month, day := t.Date()
	t = time.Date(year, month, day, 0, 0, 0, 0, t.Location())
	for t.Weekday() != time.Tuesday {
		t = t.Add(-24 * time.Hour)
	}

	return t
}

func (c *keystoneChannel) check() {
	if time.Now().After(lastTuesday(c.LastModified).Add(24 * 7 * time.Hour)) {
		c.Users = map[string]*keystone{}
	}
}

func (c *keystoneChannel) add(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, query string) bool {
	query = strings.ToLower(query)
	for dungeonID, dungeon := range dungeons {
		for _, alias := range dungeon.Aliases {
			if strings.Index(query, alias+" ") == 0 {
				query = query[len(alias)+1:]
				if len(query) == 0 {
					return false
				}

				parts := strings.Split(query, " ")

				level, err := strconv.Atoi(parts[0])
				if err != nil {
					return false
				}

				parts = parts[1:]

				depleted := false
				modifiers := []string{}

				for _, part := range parts {
					if strings.Index(part, "deplete") == 0 {
						depleted = true
					} else {
						modifiers = append(modifiers, part)
					}
				}

				c.Users[message.UserID()] = &keystone{
					User:      message.UserName(),
					Dungeon:   dungeonID,
					Level:     level,
					Depleted:  depleted,
					Modifiers: modifiers,
				}
				c.LastModified = time.Now()

				return true
			}
		}
	}
	return false
}

type keystoneList []*keystone

// Len is part of sort.Interface.
func (s keystoneList) Len() int {
	return len(s)
}

// Swap is part of sort.Interface.
func (s keystoneList) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s keystoneList) Less(i, j int) bool {
	if s[i].Level == s[j].Level {
		return dungeons[s[i].Dungeon].Name > dungeons[s[j].Dungeon].Name
	}
	return s[i].Level > s[j].Level
}

func (c *keystoneChannel) list(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if len(c.Users) == 0 {
		service.SendMessage(message.Channel(), "No keystones have been set this week.")
		return
	}

	keystones := keystoneList{}
	for _, keystone := range c.Users {
		keystones = append(keystones, keystone)
	}

	sort.Sort(keystones)

	content := ""
	for _, keystone := range keystones {
		if len(content) != 0 {
			content += "\n"
		}
		content += keystone.String()
		content += " - " + keystone.User
	}
	service.SendMessage(message.Channel(), content)
}

type keystonePlugin struct {
	sync.RWMutex
	Channels map[string]*keystoneChannel
}

// Name returns the name of the plugin.
func (p *keystonePlugin) Name() string {
	return "Keystone"
}

// Load will load plugin state from a byte array.
func (p *keystonePlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}

	if p.Channels == nil {
		p.Channels = make(map[string]*keystoneChannel)
	}

	return nil
}

// Save will save plugin state to a byte array.
func (p *keystonePlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

// Help returns a list of help strings that are printed when the user requests them.
func (p *keystonePlugin) Help(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	if p.Channels[message.Channel()] == nil {
		return nil
	}

	if !detailed {
		return []string{
			"`keystone <set <dungeon> <level> [modifiers]|list|deplete|undeplete>` - WoW Mythic Keystones.",
		}
	}

	return []string{
		"`keystone set<dungeon> <level> [modifiers]` - Set your keystone for this week.",
		"`keystone list` - Lists all this weeks keystones.",
		"`keystone deplete` - Depletes your keystone.",
		"Examples:",
		"`keystone set hov 5 teeming` - Adds a Level 5 Halls of Valor keystone with teeming.",
		"`keystone set eye of azshara 2 depleted` - Adds a depleted Level 2 Eye of Azshara keystone.",
	}
}

// Message handler.
func (p *keystonePlugin) Message(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	defer bruxism.MessageRecover()
	if !service.IsMe(message) {
		messageChannel := message.Channel()

		if service.IsBotOwner(message) && bruxism.MatchesCommand(service, "keystone", message) {
			_, parts := bruxism.ParseCommand(service, message)

			p.Lock()
			defer p.Unlock()

			switch parts[0] {
			case "start":
				p.Channels[messageChannel] = &keystoneChannel{
					Users: map[string]*keystone{},
				}
				service.SendMessage(messageChannel, "This channel is now tracking mythic keystones.")
			case "stop":
				delete(p.Channels, messageChannel)
				service.SendMessage(messageChannel, "This channel is no longer tracking mythic keystones.")
			}
		} else if channel, ok := p.Channels[messageChannel]; ok {
			channel.check()
			parts := strings.Split(strings.ToLower(message.Message()), " ")
			if len(parts) < 2 || parts[0] != "keystone" {
				return
			}

			switch parts[1] {
			case "set":
				if len(parts) > 2 && channel.add(bot, service, message, message.Message()[len("keystone set "):]) {
					channel.list(bot, service, message)
				} else {
					service.SendMessage(messageChannel, "Invalid keystone. Eg: `keystone hall of valor 3 sanguine`")
				}
			case "list":
				channel.list(bot, service, message)
			case "deplete":
				keystone := channel.Users[message.UserID()]
				if keystone == nil {
					service.SendMessage(messageChannel, "You haven't set a keystone this week.")
				} else {
					keystone.Depleted = true
					keystone.User = message.UserName()
					service.SendMessage(messageChannel, fmt.Sprintf("Keystone depleted: %s", keystone.String()))
				}
			case "undeplete":
				keystone := channel.Users[message.UserID()]
				if keystone == nil {
					service.SendMessage(messageChannel, "You haven't set a keystone this week.")
				} else {
					keystone.Depleted = false
					keystone.User = message.UserName()
					service.SendMessage(messageChannel, fmt.Sprintf("Keystone undepleted: %s", keystone.String()))
				}
			}
		}
	}
}

// Stats will return the stats for a plugin.
func (p *keystonePlugin) Stats(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) []string {
	return nil
}

// New will create a new wormhole plugin.
func New() bruxism.Plugin {
	return &keystonePlugin{}
}
<|endoftext|>


<commit_before>// Copyright 2013 Ivan Danyliuk
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xml

import (
	"reflect"
	"testing"
	"time"
)

type SubStructXml2Rpc struct {
	Foo  int
	Bar  string
	Data []int
}

type StructXml2Rpc struct {
	Int    int
	Float  float64
	Str    string
	Bool   bool
	Sub    SubStructXml2Rpc
	Time   time.Time
	Base64 []byte
}

func TestXML2RPC(t *testing.T) {
	req := new(StructXml2Rpc)
	err := XML2RPC("<methodCall><methodName>Some.Method</methodName><params><param><value><i4>123</i4></value></param><param><value><double>3.145926</double></value></param><param><value><string>Hello, World!</string></value></param><param><value><boolean>0</boolean></value></param><param><value><struct><member><name>Foo</name><value><int>42</int></value></member><member><name>Bar</name><value><string>I'm Bar</string></value></member><member><name>Data</name><value><array><data><value><int>1</int></value><value><int>2</int></value><value><int>3</int></value></data></array></value></member></struct></value></param><param><value><dateTime.iso8601>20120717T14:08:55</dateTime.iso8601></value></param><param><value><base64>eW91IGNhbid0IHJlYWQgdGhpcyE=</base64></value></param></params></methodCall>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructXml2Rpc{123, 3.145926, "Hello, World!", false, SubStructXml2Rpc{42, "I'm Bar", []int{1, 2, 3}}, time.Date(2012, time.July, 17, 14, 8, 55, 0, time.Local), []byte("you can't read this!")}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructSpecialCharsXml2Rpc struct {
	String1 string
}

func TestXML2RPCSpecialChars(t *testing.T) {
	req := new(StructSpecialCharsXml2Rpc)
	err := XML2RPC("<methodResponse><params><param><value><string> & " < > </string></value></param></params></methodResponse>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructSpecialCharsXml2Rpc{" & \" < > "}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructNilXml2Rpc struct {
	Ptr *int
}

func TestXML2RPCNil(t *testing.T) {
	req := new(StructNilXml2Rpc)
	err := XML2RPC("<methodResponse><params><param><value><nil/></value></param></params></methodResponse>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructNilXml2Rpc{nil}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructXml2RpcSubArgs struct {
	String1 string
	String2 string
	Id      int
}

type StructXml2RpcHelloArgs struct {
	Args StructXml2RpcSubArgs
}

func TestXML2RPCLowercasedMethods(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	err := XML2RPC("<methodCall><params><param><value><struct><member><name>string1</name><value><string>I'm a first string</string></value></member><member><name>string2</name><value><string>I'm a second string</string></value></member><member><name>id</name><value><int>1</int></value></member></struct></value></param></params></methodCall>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	args := StructXml2RpcSubArgs{"I'm a first string", "I'm a second string", 1}
	expected_req := &StructXml2RpcHelloArgs{args}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

func TestXML2PRCFaultCall(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	data := `<?xmlversion="1.0"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>116</int></value></member><member><name>faultString</name><value><string>Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
</string></value></member></struct></value></fault></methodResponse>`

	errstr := `116: Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
`

	err := XML2RPC(data, req)

	if err == nil {
		t.Error("should have found report error")
	}

	if err.Error() != errstr {
		t.Errorf("error should be:\n\n%s\n\nbut got:\n\n%s\n", errstr, err.Error())
	}

}

func TestXML2PRCISO(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	data := `<?xml version="1.0"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>116</int></value></member><member><name>faultString</name><value><string>Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
</string></value></member></struct></value></fault></methodResponse>`

	errstr := `116: Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
`

	err := XML2RPC(data, req)

	if err == nil {
		t.Error("should have found report error")
	}

	if err.Error() != errstr {
		t.Errorf("error should be:\n\n%s\n\nbut got:\n\n%s\n", errstr, err.Error())
	}
}
<commit_msg>Tests improvement<commit_after>// Copyright 2013 Ivan Danyliuk
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xml

import (
	"reflect"
	"testing"
	"time"
)

type SubStructXml2Rpc struct {
	Foo  int
	Bar  string
	Data []int
}

type StructXml2Rpc struct {
	Int    int
	Float  float64
	Str    string
	Bool   bool
	Sub    SubStructXml2Rpc
	Time   time.Time
	Base64 []byte
}

func TestXML2RPC(t *testing.T) {
	req := new(StructXml2Rpc)
	err := XML2RPC("<methodCall><methodName>Some.Method</methodName><params><param><value><i4>123</i4></value></param><param><value><double>3.145926</double></value></param><param><value><string>Hello, World!</string></value></param><param><value><boolean>0</boolean></value></param><param><value><struct><member><name>Foo</name><value><int>42</int></value></member><member><name>Bar</name><value><string>I'm Bar</string></value></member><member><name>Data</name><value><array><data><value><int>1</int></value><value><int>2</int></value><value><int>3</int></value></data></array></value></member></struct></value></param><param><value><dateTime.iso8601>20120717T14:08:55</dateTime.iso8601></value></param><param><value><base64>eW91IGNhbid0IHJlYWQgdGhpcyE=</base64></value></param></params></methodCall>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructXml2Rpc{123, 3.145926, "Hello, World!", false, SubStructXml2Rpc{42, "I'm Bar", []int{1, 2, 3}}, time.Date(2012, time.July, 17, 14, 8, 55, 0, time.Local), []byte("you can't read this!")}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructSpecialCharsXml2Rpc struct {
	String1 string
}

func TestXML2RPCSpecialChars(t *testing.T) {
	req := new(StructSpecialCharsXml2Rpc)
	err := XML2RPC("<methodResponse><params><param><value><string> & " < > </string></value></param></params></methodResponse>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructSpecialCharsXml2Rpc{" & \" < > "}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructNilXml2Rpc struct {
	Ptr *int
}

func TestXML2RPCNil(t *testing.T) {
	req := new(StructNilXml2Rpc)
	err := XML2RPC("<methodResponse><params><param><value><nil/></value></param></params></methodResponse>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	expected_req := &StructNilXml2Rpc{nil}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

type StructXml2RpcSubArgs struct {
	String1 string
	String2 string
	Id      int
}

type StructXml2RpcHelloArgs struct {
	Args StructXml2RpcSubArgs
}

func TestXML2RPCLowercasedMethods(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	err := XML2RPC("<methodCall><params><param><value><struct><member><name>string1</name><value><string>I'm a first string</string></value></member><member><name>string2</name><value><string>I'm a second string</string></value></member><member><name>id</name><value><int>1</int></value></member></struct></value></param></params></methodCall>", req)
	if err != nil {
		t.Error("XML2RPC conversion failed", err)
	}
	args := StructXml2RpcSubArgs{"I'm a first string", "I'm a second string", 1}
	expected_req := &StructXml2RpcHelloArgs{args}
	if !reflect.DeepEqual(req, expected_req) {
		t.Error("XML2RPC conversion failed")
		t.Error("Expected", expected_req)
		t.Error("Got", req)
	}
}

func TestXML2PRCFaultCall(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	data := `<?xmlversion="1.0"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>116</int></value></member><member><name>faultString</name><value><string>Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
</string></value></member></struct></value></fault></methodResponse>`

	errstr := `Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
`

	err := XML2RPC(data, req)

	fault, ok := err.(Fault)
	if !ok {
		t.Errorf("error should be of concrete type Fault, but got %v", err)
	} else {
		if fault.Code != 116 {
			t.Errorf("expected fault.Code to be %d, but got %d", 116, fault.Code)
		}
		if fault.String != errstr {
			t.Errorf("fault.String should be:\n\n%s\n\nbut got:\n\n%s\n", errstr, fault.String)
		}
	}
}

func TestXML2PRCISO(t *testing.T) {
	req := new(StructXml2RpcHelloArgs)
	data := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>116</int></value></member><member><name>faultString</name><value><string>Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]
</string></value></member></struct></value></fault></methodResponse>`

	errstr := `Error
Requiredattribute'user'notfound:
[{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}]`

	err := XML2RPC(data, req)

	fault, ok := err.(Fault)
	if !ok {
		t.Errorf("error should be of concrete type Fault, but got %v", err)
	} else {
		if fault.Code != 116 {
			t.Errorf("expected fault.Code to be %d, but got %d", 116, fault.Code)
		}
		if fault.String != errstr {
			t.Errorf("fault.String should be:\n\n%s\n\nbut got:\n\n%s\n", errstr, fault.String)
		}
	}
}
<|endoftext|>


<commit_before>// Package compression defines a response compressing Handler.
// It compresses the body of the http response sent back to a client.
package compression

import (
	"compress/gzip"
	"io"
	"net/http"
	"strings"
	"sync"

	"github.com/atdiar/goroutine/execution"
	"github.com/atdiar/xhttp"
)

// Gzipper defines the structure of the response compressing Handler.
type Gzipper struct {
	pool *sync.Pool // useful here to recycle gzip buffers
	skip map[string]bool
	next xhttp.Handler
}

// CallNext registers a next request Handler to be called by ServeHTTP method.
// It returns the linked reqest Handlers.
func (g Gzipper) CallNext(h xhttp.Handler) xhttp.HandlerLinker {
	g.next = h
	return g
}

// NewHandler returns a response compressing Handler.
func NewHandler() Gzipper {
	g := Gzipper{}
	g.skip = map[string]bool{
		"GET":     false,
		"POST":    false,
		"PUT":     false,
		"PATCH":   false,
		"DELETE":  false,
		"HEAD":    false,
		"OPTIONS": false,
	}
	g.pool = &sync.Pool{New: func() interface{} { return gzip.NewWriter(nil) }}
	return g
}

// Skip is used to disable gzip compression for a given http method.
func (g Gzipper) Skip(method string) Gzipper {
	if _, ok := g.skip[strings.ToUpper(method)]; !ok {
		panic(method + " is not a valid method")
	}
	g.skip[method] = true
	return g
}

// This is a type of wrapper around a http.ResponseWriter which buffers data
// before compressing the whole and writing.
type compressingWriter struct {
	io.WriteCloser
	http.ResponseWriter
	p *sync.Pool
}

func newcompressingWriter(w http.ResponseWriter, p *sync.Pool) compressingWriter {
	w1 := p.Get()
	w2 := w1.(*gzip.Writer)
	w2.Reset(w)
	return compressingWriter{w2, w, p}
}

// Write is using the gzip writer Write method.
func (cw compressingWriter) Write(b []byte) (int, error) {
	if cw.ResponseWriter.Header().Get("Content-Type") == "" {
		cw.ResponseWriter.Header().Set("Content-Type", http.DetectContentType(b))
		cw.ResponseWriter.Header().Del("Content-Length")
	}
	return cw.WriteCloser.Write(b)
}

// Close flushes the compressed bytestring to the underlying ResponseWriter.
// Then it releases the gzip.Writer, putting it back into the Pool.
func (cw compressingWriter) Close() error {
	z := cw.WriteCloser.(*gzip.Writer)
	err := z.Flush()
	cw.p.Put(z)
	return err
}

// ServeHTTP handles a http.Request by gzipping the http response body and
// setting the right http Headers.
func (g Gzipper) ServeHTTP(ctx execution.Context, w http.ResponseWriter, req *http.Request) {
	if mustSkip, exist := g.skip[strings.ToUpper(req.Method)]; exist && mustSkip {
		if g.next != nil {
			g.next.ServeHTTP(ctx, w, req)
		}
		return
	}
	// We create a compressingWriter that will enable
	//the response writing w/ Compression.
	wc := newcompressingWriter(w, g.pool)

	w.Header().Add("Vary", "Accept-Encoding")
	if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
		if g.next != nil {
			g.next.ServeHTTP(ctx, w, req)
		}
		return
	}
	wc.Header().Set("Content-Encoding", "gzip")
	// All the conditions are present : we shall compress the data before writing
	// it out.
	if g.next != nil {
		g.next.ServeHTTP(ctx, wc, req)
	}
	err := wc.Close()
	if err != nil {
		ctx.Cancel()
		panic(err)
	}
}
<commit_msg>Add minor changes.<commit_after>// Package compression defines a response compressing Handler.
// It compresses the body of the http response sent back to a client.
package compression

import (
	"compress/gzip"
	"io"
	"net/http"
	"strings"
	"sync"

	"github.com/atdiar/goroutine/execution"
	"github.com/atdiar/xhttp"
)

// Gzipper defines the structure of the response compressing Handler.
type Gzipper struct {
	pool *sync.Pool // useful here to recycle gzip buffers
	skip map[string]bool
	next xhttp.Handler
}

// CallNext registers a next request Handler to be called by ServeHTTP method.
// It returns the linked reqest Handlers.
func (g Gzipper) Link(h xhttp.Handler) xhttp.HandlerLinker {
	g.next = h
	return g
}

// NewHandler returns a response compressing Handler.
func NewHandler() Gzipper {
	g := Gzipper{}
	g.skip = map[string]bool{
		"GET":     false,
		"POST":    false,
		"PUT":     false,
		"PATCH":   false,
		"DELETE":  false,
		"HEAD":    false,
		"OPTIONS": false,
	}
	g.pool = &sync.Pool{New: func() interface{} { return gzip.NewWriter(nil) }}
	return g
}

// Skip is used to disable gzip compression for a given http method.
func (g Gzipper) Skip(method string) Gzipper {
	if _, ok := g.skip[strings.ToUpper(method)]; !ok {
		panic(method + " is not a valid method")
	}
	g.skip[method] = true
	return g
}

// This is a type of wrapper around a http.ResponseWriter which buffers data
// before compressing the whole and writing.
type compressingWriter struct {
	io.WriteCloser
	http.ResponseWriter
	p *sync.Pool
}

func newcompressingWriter(w http.ResponseWriter, p *sync.Pool) compressingWriter {
	w1 := p.Get()
	w2 := w1.(*gzip.Writer)
	w2.Reset(w)
	return compressingWriter{w2, w, p}
}

// Write is using the gzip writer Write method.
func (cw compressingWriter) Write(b []byte) (int, error) {
	if cw.ResponseWriter.Header().Get("Content-Type") == "" {
		cw.ResponseWriter.Header().Set("Content-Type", http.DetectContentType(b))
		cw.ResponseWriter.Header().Del("Content-Length")
	}
	return cw.WriteCloser.Write(b)
}

// Close flushes the compressed bytestring to the underlying ResponseWriter.
// Then it releases the gzip.Writer, putting it back into the Pool.
func (cw compressingWriter) Close() error {
	z := cw.WriteCloser.(*gzip.Writer)
	err := z.Flush()
	cw.p.Put(z)
	return err
}

// ServeHTTP handles a http.Request by gzipping the http response body and
// setting the right http Headers.
func (g Gzipper) ServeHTTP(ctx execution.Context, w http.ResponseWriter, req *http.Request) {
	if mustSkip, exist := g.skip[strings.ToUpper(req.Method)]; exist && mustSkip {
		if g.next != nil {
			g.next.ServeHTTP(ctx, w, req)
		}
		return
	}
	// We create a compressingWriter that will enable
	//the response writing w/ Compression.
	wc := newcompressingWriter(w, g.pool)

	w.Header().Add("Vary", "Accept-Encoding")
	if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
		if g.next != nil {
			g.next.ServeHTTP(ctx, w, req)
		}
		return
	}
	wc.Header().Set("Content-Encoding", "gzip")
	// All the conditions are present : we shall compress the data before writing
	// it out.
	if g.next != nil {
		g.next.ServeHTTP(ctx, wc, req)
	}
	err := wc.Close()
	if err != nil {
		ctx.Panic(err)
	}
}
<|endoftext|>


<commit_before>package tls

import (
	"crypto/tls"
	"fmt"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/pachyderm/pachyderm/src/client/pkg/errors"

	log "github.com/sirupsen/logrus"
)

// CertLoader provides simple hot TLS certificate reloading by checking for a renewed certificate at a configurable interval
type CertLoader struct {
	certPath        string
	keyPath         string
	refreshInterval time.Duration

	// cert is the current cached *tls.Certificate. It should only be accessed with atomic methods because it may be updated by the cert reloading routine.
	cert     unsafe.Pointer
	stopChan chan interface{}
	stopped  bool
}

// NewCertLoader creates a new CertLoader to refresh the specified TLS key at a fixed interval
func NewCertLoader(certPath, keyPath string, refreshInterval time.Duration) *CertLoader {
	return &CertLoader{
		certPath:        certPath,
		keyPath:         keyPath,
		refreshInterval: refreshInterval,
	}
}

// LoadAndStart ensures the current TLS certificate is loaded and starts the reload routine to poll for renewed certificates
func (l *CertLoader) LoadAndStart() error {
	if err := l.loadCertificate(); err != nil {
		return err
	}
	go l.reloadRoutine()
	return nil
}

// Stop signals the reloading routine to stop
func (l *CertLoader) Stop() {
	if l.stopped {
		return
	}
	l.stopped = true
	close(l.stopChan)
}

// GetCertificate gets the currently cached certificate and fulfills
func (l *CertLoader) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
	certPtr := atomic.LoadPointer(&l.cert)
	cert := (*tls.Certificate)(certPtr)
	if cert == nil {
		return nil, fmt.Errorf("No cached TLS certificate available")
	}
	return cert, nil
}

func (l *CertLoader) reloadRoutine() {
	t := time.NewTicker(l.refreshInterval)
	for {
		select {
		case <-t.C:
			err := l.loadCertificate()
			if err != nil {
				log.Error("Unable to load TLS certificate", err)
			}
		case <-l.stopChan:
			return
		}
	}
}

func (l *CertLoader) loadCertificate() error {
	log.Debugf("Reloading TLS keypair - %q %q", l.certPath, l.keyPath)
	cert, err := tls.LoadX509KeyPair(l.certPath, l.keyPath)
	if err != nil {
		return errors.Wrapf(err, "Unable to load keypair")
	}
	atomic.StorePointer(&l.cert, unsafe.Pointer(&cert))
	return nil
}
<commit_msg>Fix lint error<commit_after>package tls

import (
	"crypto/tls"
	"fmt"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/pachyderm/pachyderm/src/client/pkg/errors"

	log "github.com/sirupsen/logrus"
)

// CertLoader provides simple hot TLS certificate reloading by checking for a renewed certificate at a configurable interval
type CertLoader struct {
	certPath        string
	keyPath         string
	refreshInterval time.Duration

	// cert is the current cached *tls.Certificate. It should only be accessed with atomic methods because it may be updated by the cert reloading routine.
	cert     unsafe.Pointer
	stopChan chan interface{}
	stopped  bool
}

// NewCertLoader creates a new CertLoader to refresh the specified TLS key at a fixed interval
func NewCertLoader(certPath, keyPath string, refreshInterval time.Duration) *CertLoader {
	return &CertLoader{
		certPath:        certPath,
		keyPath:         keyPath,
		refreshInterval: refreshInterval,
	}
}

// LoadAndStart ensures the current TLS certificate is loaded and starts the reload routine to poll for renewed certificates
func (l *CertLoader) LoadAndStart() error {
	if err := l.loadCertificate(); err != nil {
		return err
	}
	go l.reloadRoutine()
	return nil
}

// Stop signals the reloading routine to stop
func (l *CertLoader) Stop() {
	if l.stopped {
		return
	}
	l.stopped = true
	close(l.stopChan)
}

// GetCertificate gets the currently cached certificate and fulfills
func (l *CertLoader) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
	certPtr := atomic.LoadPointer(&l.cert)
	cert := (*tls.Certificate)(certPtr)
	if cert == nil {
		return nil, fmt.Errorf("no cached TLS certificate available")
	}
	return cert, nil
}

func (l *CertLoader) reloadRoutine() {
	t := time.NewTicker(l.refreshInterval)
	for {
		select {
		case <-t.C:
			err := l.loadCertificate()
			if err != nil {
				log.Error("Unable to load TLS certificate", err)
			}
		case <-l.stopChan:
			return
		}
	}
}

func (l *CertLoader) loadCertificate() error {
	log.Debugf("Reloading TLS keypair - %q %q", l.certPath, l.keyPath)
	cert, err := tls.LoadX509KeyPair(l.certPath, l.keyPath)
	if err != nil {
		return errors.Wrapf(err, "unable to load keypair")
	}
	atomic.StorePointer(&l.cert, unsafe.Pointer(&cert))
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package handlers provides a set of functions to manipulate http.HandlerFunc
// at a route level. cf github.com/taironas/gonawin/gonawin/main.go for details
// on how to use it.
//
package handlers

import (
	"errors"
	"net/http"

	"appengine"

	"github.com/taironas/gonawin/helpers"
	"github.com/taironas/gonawin/helpers/auth"
	"github.com/taironas/gonawin/helpers/log"

	mdl "github.com/taironas/gonawin/models"
)

type ErrorHandlerFunc func(http.ResponseWriter, *http.Request) error

// Error handler returns the proper error handler function with respecto to the error rised by the function called.
func ErrorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err == nil {
			return
		}
		switch err.(type) {
		case *helpers.BadRequest:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case *helpers.NotFound:
			http.Error(w, err.Error(), http.StatusNotFound)
		case *helpers.Forbidden:
			http.Error(w, err.Error(), http.StatusForbidden)
		case *helpers.Unauthorized:
			http.Error(w, err.Error(), http.StatusUnauthorized)
		case *helpers.InternalServerError:
			http.Error(w, err.Error(), http.StatusInternalServerError)
		default:
			c := appengine.NewContext(r)
			log.Errorf(c, "%v", err)
			http.Error(w, "Sorry, something went wrong.", http.StatusInternalServerError)
		}
	}
}

// Authorized runs the function pass by parameter and checks authentication data prior to any call.
// Will rise a bad request error handler if authentication fails.
func Authorized(f func(w http.ResponseWriter, r *http.Request, u *mdl.User) error) ErrorHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		var user *mdl.User
		if auth.KOfflineMode {
			user = auth.CurrentOfflineUser(r, appengine.NewContext(r))
		} else {
			user = auth.CheckAuthenticationData(r)
		}

		if user == nil {
			return &helpers.BadRequest{Err: errors.New("Bad Authentication data")}
		} else {
			return f(w, r, user)
		}
	}
}

// Admin Authorized runs the function pass by parameter and checks authentication data prior to any call.
// Will rise a bad request error handler if authentication fails. User should be a gonawin admin .
func AdminAuthorized(f func(w http.ResponseWriter, r *http.Request, u *mdl.User) error) ErrorHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		var user *mdl.User
		if auth.KOfflineMode {
			user = auth.CurrentOfflineUser(r, appengine.NewContext(r))
		} else {
			user = auth.CheckAuthenticationData(r)
			if user == nil {
				return &helpers.BadRequest{Err: errors.New("Bad Authentication data")}
			}
		}
		if !auth.IsGonawinAdmin(appengine.NewContext(r)) { //user) {
			return &helpers.Forbidden{Err: errors.New(helpers.ErrorCodeSessionsForbiden)}
		}
		return f(w, r, user)
	}
}
<commit_msg>go lint helpers/handlers/handlers.go<commit_after>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package handlers provides a set of functions to manipulate http.HandlerFunc
// at a route level. cf github.com/taironas/gonawin/gonawin/main.go for details
// on how to use it.
//
package handlers

import (
	"errors"
	"net/http"

	"appengine"

	"github.com/taironas/gonawin/helpers"
	"github.com/taironas/gonawin/helpers/auth"
	"github.com/taironas/gonawin/helpers/log"

	mdl "github.com/taironas/gonawin/models"
)

// ErrorHandlerFunc is a handler returning an error.
//
type ErrorHandlerFunc func(http.ResponseWriter, *http.Request) error

// ErrorHandler returns the proper error handler function with respecto to the error rised by the function called.
//
func ErrorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err == nil {
			return
		}
		switch err.(type) {
		case *helpers.BadRequest:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case *helpers.NotFound:
			http.Error(w, err.Error(), http.StatusNotFound)
		case *helpers.Forbidden:
			http.Error(w, err.Error(), http.StatusForbidden)
		case *helpers.Unauthorized:
			http.Error(w, err.Error(), http.StatusUnauthorized)
		case *helpers.InternalServerError:
			http.Error(w, err.Error(), http.StatusInternalServerError)
		default:
			c := appengine.NewContext(r)
			log.Errorf(c, "%v", err)
			http.Error(w, "Sorry, something went wrong.", http.StatusInternalServerError)
		}
	}
}

// Authorized runs the function pass by parameter and checks authentication data prior to any call.
// Will rise a bad request error handler if authentication fails.
//
func Authorized(f func(w http.ResponseWriter, r *http.Request, u *mdl.User) error) ErrorHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		var user *mdl.User
		if auth.KOfflineMode {
			user = auth.CurrentOfflineUser(r, appengine.NewContext(r))
		} else {
			user = auth.CheckAuthenticationData(r)
		}

		if user == nil {
			return &helpers.BadRequest{Err: errors.New("Bad Authentication data")}
		}

		return f(w, r, user)
	}
}

// AdminAuthorized runs the function pass by parameter and checks authentication data prior to any call.
// Will rise a bad request error handler if authentication fails. User should be a gonawin admin .
//
func AdminAuthorized(f func(w http.ResponseWriter, r *http.Request, u *mdl.User) error) ErrorHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		var user *mdl.User
		if auth.KOfflineMode {
			user = auth.CurrentOfflineUser(r, appengine.NewContext(r))
		} else {
			user = auth.CheckAuthenticationData(r)
			if user == nil {
				return &helpers.BadRequest{Err: errors.New("Bad Authentication data")}
			}
		}
		if !auth.IsGonawinAdmin(appengine.NewContext(r)) { //user) {
			return &helpers.Forbidden{Err: errors.New(helpers.ErrorCodeSessionsForbiden)}
		}
		return f(w, r, user)
	}
}
<|endoftext|>


<commit_before>package history

import (
	"database/sql"
	"testing"

	"github.com/stellar/horizon/test"
)

func TestLedgerQueries(t *testing.T) {
	tt := test.Start(t).Scenario("base")
	defer tt.Finish()
	q := &Q{tt.HorizonRepo()}

	// Test LedgerBySequence
	var l Ledger
	err := q.LedgerBySequence(&l, 3)
	tt.Assert.NoError(err)

	err = q.LedgerBySequence(&l, 100000)
	tt.Assert.Equal(err, sql.ErrNoRows)

	// Test Ledgers()
	ls := []Ledger{}
	err = q.Ledgers().Select(&ls)

	if tt.Assert.NoError(err) {
		tt.Assert.Len(ls, 3)
	}

	// LedgersBySequence
	err = q.LedgersBySequence(&ls, 1, 2, 3)

	if tt.Assert.NoError(err) {
		tt.Assert.Len(ls, 3)
	}
}
<commit_msg>db2/history:  testing LedgersBySequence<commit_after>package history

import (
	"database/sql"
	"testing"

	"github.com/stellar/horizon/test"
)

func TestLedgerQueries(t *testing.T) {
	tt := test.Start(t).Scenario("base")
	defer tt.Finish()
	q := &Q{tt.HorizonRepo()}

	// Test LedgerBySequence
	var l Ledger
	err := q.LedgerBySequence(&l, 3)
	tt.Assert.NoError(err)

	err = q.LedgerBySequence(&l, 100000)
	tt.Assert.Equal(err, sql.ErrNoRows)

	// Test Ledgers()
	ls := []Ledger{}
	err = q.Ledgers().Select(&ls)

	if tt.Assert.NoError(err) {
		tt.Assert.Len(ls, 3)
	}

	// LedgersBySequence
	err = q.LedgersBySequence(&ls, 1, 2, 3)

	if tt.Assert.NoError(err) {
		tt.Assert.Len(ls, 3)

		foundSeqs := make([]int32, len(ls))
		for i := range ls {
			foundSeqs[i] = ls[i].Sequence
		}

		tt.Assert.Contains(foundSeqs, int32(1))
		tt.Assert.Contains(foundSeqs, int32(2))
		tt.Assert.Contains(foundSeqs, int32(3))
	}
}
<|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 common

import (
	apps "k8s.io/api/apps/v1beta2"
	batch "k8s.io/api/batch/v1"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// FilterPodsByControllerResource returns a subset of pods controlled by given deployment.
func FilterDeploymentPodsByOwnerReference(deployment apps.Deployment, allRS []apps.ReplicaSet,
	allPods []v1.Pod) []v1.Pod {
	var matchingPods []v1.Pod

	rsTemplate := v1.PodTemplateSpec{
		ObjectMeta: deployment.Spec.Template.ObjectMeta,
		Spec:       deployment.Spec.Template.Spec,
	}

	for _, rs := range allRS {
		if EqualIgnoreHash(rs.Spec.Template, rsTemplate) {
			matchingPods = FilterPodsByControllerRef(&rs, allPods)
		}
	}

	return matchingPods
}

// FilterPodsByControllerRef returns a subset of pods controlled by given controller resource, excluding deployments.
func FilterPodsByControllerRef(owner metav1.Object, allPods []v1.Pod) []v1.Pod {
	var matchingPods []v1.Pod
	for _, pod := range allPods {
		if metav1.IsControlledBy(&pod, owner) {
			matchingPods = append(matchingPods, pod)
		}
	}
	return matchingPods
}

func FilterPodsForJob(job batch.Job, pods []v1.Pod) []v1.Pod {
	result := make([]v1.Pod, 0)
	for _, pod := range pods {
		if pod.Namespace == job.Namespace && pod.Labels["controller-uid"] ==
			job.Spec.Selector.MatchLabels["controller-uid"] {
			result = append(result, pod)
		}
	}

	return result
}

// GetContainerImages returns container image strings from the given pod spec.
func GetContainerImages(podTemplate *v1.PodSpec) []string {
	var containerImages []string
	for _, container := range podTemplate.Containers {
		containerImages = append(containerImages, container.Image)
	}
	return containerImages
}

// GetInitContainerImages returns init container image strings from the given pod spec.
func GetInitContainerImages(podTemplate *v1.PodSpec) []string {
	var initContainerImages []string
	for _, initContainer := range podTemplate.InitContainers {
		initContainerImages = append(initContainerImages, initContainer.Image)
	}
	return initContainerImages
}

// GetContainerNames returns the container image name without the version number from the given pod spec.
func GetContainerNames(podTemplate *v1.PodSpec) []string {
	var containerNames []string
	for _, container := range podTemplate.Containers {
		containerNames = append(containerNames, container.Name)
	}
	return containerNames
}

// GetInitContainerNames returns the init container image name without the version number from the given pod spec.
func GetInitContainerNames(podTemplate *v1.PodSpec) []string {
	var initContainerNames []string
	for _, initContainer := range podTemplate.InitContainers {
		initContainerNames = append(initContainerNames, initContainer.Name)
	}
	return initContainerNames
}

// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because the hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// Note that we assume input podTemplateSpecs contain non-empty labels
func EqualIgnoreHash(template1, template2 v1.PodTemplateSpec) bool {
	// First, compare template.Labels (ignoring hash)
	labels1, labels2 := template1.Labels, template2.Labels
	if len(labels1) > len(labels2) {
		labels1, labels2 = labels2, labels1
	}
	// We make sure len(labels2) >= len(labels1)
	for k, v := range labels2 {
		if labels1[k] != v && k != apps.DefaultDeploymentUniqueLabelKey {
			return false
		}
	}
	// Then, compare the templates without comparing their labels
	template1.Labels, template2.Labels = nil, nil
	return equality.Semantic.DeepEqual(template1, template2)
}
<commit_msg>fix: deployment to rs reference (#2934)<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 common

import (
	apps "k8s.io/api/apps/v1beta2"
	batch "k8s.io/api/batch/v1"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// FilterPodsByControllerResource returns a subset of pods controlled by given deployment.
func FilterDeploymentPodsByOwnerReference(deployment apps.Deployment, allRS []apps.ReplicaSet,
	allPods []v1.Pod) []v1.Pod {
	var matchingPods []v1.Pod
  for _, rs := range allRS {
    if metav1.IsControlledBy(&rs, &deployment) {
      matchingPods = append(matchingPods, FilterPodsByControllerRef(&rs, allPods)...)
    }
  }

	return matchingPods
}

// FilterPodsByControllerRef returns a subset of pods controlled by given controller resource, excluding deployments.
func FilterPodsByControllerRef(owner metav1.Object, allPods []v1.Pod) []v1.Pod {
	var matchingPods []v1.Pod
	for _, pod := range allPods {
		if metav1.IsControlledBy(&pod, owner) {
			matchingPods = append(matchingPods, pod)
		}
	}
	return matchingPods
}

func FilterPodsForJob(job batch.Job, pods []v1.Pod) []v1.Pod {
	result := make([]v1.Pod, 0)
	for _, pod := range pods {
		if pod.Namespace == job.Namespace && pod.Labels["controller-uid"] ==
			job.Spec.Selector.MatchLabels["controller-uid"] {
			result = append(result, pod)
		}
	}

	return result
}

// GetContainerImages returns container image strings from the given pod spec.
func GetContainerImages(podTemplate *v1.PodSpec) []string {
	var containerImages []string
	for _, container := range podTemplate.Containers {
		containerImages = append(containerImages, container.Image)
	}
	return containerImages
}

// GetInitContainerImages returns init container image strings from the given pod spec.
func GetInitContainerImages(podTemplate *v1.PodSpec) []string {
	var initContainerImages []string
	for _, initContainer := range podTemplate.InitContainers {
		initContainerImages = append(initContainerImages, initContainer.Image)
	}
	return initContainerImages
}

// GetContainerNames returns the container image name without the version number from the given pod spec.
func GetContainerNames(podTemplate *v1.PodSpec) []string {
	var containerNames []string
	for _, container := range podTemplate.Containers {
		containerNames = append(containerNames, container.Name)
	}
	return containerNames
}

// GetInitContainerNames returns the init container image name without the version number from the given pod spec.
func GetInitContainerNames(podTemplate *v1.PodSpec) []string {
	var initContainerNames []string
	for _, initContainer := range podTemplate.InitContainers {
		initContainerNames = append(initContainerNames, initContainer.Name)
	}
	return initContainerNames
}

// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because the hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// Note that we assume input podTemplateSpecs contain non-empty labels
func EqualIgnoreHash(template1, template2 v1.PodTemplateSpec) bool {
	// First, compare template.Labels (ignoring hash)
	labels1, labels2 := template1.Labels, template2.Labels
	if len(labels1) > len(labels2) {
		labels1, labels2 = labels2, labels1
	}
	// We make sure len(labels2) >= len(labels1)
	for k, v := range labels2 {
		if labels1[k] != v && k != apps.DefaultDeploymentUniqueLabelKey {
			return false
		}
	}
	// Then, compare the templates without comparing their labels
	template1.Labels, template2.Labels = nil, nil
	return equality.Semantic.DeepEqual(template1, template2)
}
<|endoftext|>


<commit_before>package outbound

import (
	"context"
	"errors"
	"io"
	"net"
	"time"

	"v2ray.com/core/app"
	"v2ray.com/core/app/proxyman"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/proxy"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/ray"
)

type Handler struct {
	config          *proxyman.OutboundHandlerConfig
	senderSettings  *proxyman.SenderConfig
	proxy           proxy.Outbound
	outboundManager proxyman.OutboundHandlerManager
}

func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*Handler, error) {
	h := &Handler{
		config: config,
	}
	space := app.SpaceFromContext(ctx)
	if space == nil {
		return nil, errors.New("Proxyman|OutboundHandler: No space in context.")
	}
	space.OnInitialize(func() error {
		ohm := proxyman.OutboundHandlerManagerFromSpace(space)
		if ohm == nil {
			return errors.New("Proxyman|OutboundHandler: No OutboundManager in space.")
		}
		h.outboundManager = ohm
		return nil
	})

	if config.SenderSettings != nil {
		senderSettings, err := config.SenderSettings.GetInstance()
		if err != nil {
			return nil, err
		}
		switch s := senderSettings.(type) {
		case *proxyman.SenderConfig:
			h.senderSettings = s
		default:
			return nil, errors.New("Proxyman|DefaultOutboundHandler: settings is not SenderConfig.")
		}
	}

	proxyHandler, err := config.GetProxyHandler(proxy.ContextWithDialer(ctx, h))
	if err != nil {
		return nil, err
	}

	h.proxy = proxyHandler
	return h, nil
}

func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
	ctx = proxy.ContextWithDialer(ctx, h)
	h.proxy.Process(ctx, outboundRay)
}

func (h *Handler) Dial(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
	if h.senderSettings != nil {
		if h.senderSettings.ProxySettings.HasTag() {
			tag := h.senderSettings.ProxySettings.Tag
			handler := h.outboundManager.GetHandler(tag)
			if handler != nil {
				log.Info("Proxyman|OutboundHandler: Proxying to ", dest)
				ctx = proxy.ContextWithDestination(ctx, dest)
				stream := ray.NewRay(ctx)
				go handler.Dispatch(ctx, stream)
				return NewConnection(stream), nil
			}

			log.Warning("Proxyman|OutboundHandler: Failed to get outbound handler with tag: ", tag)
		}

		if h.senderSettings.Via != nil {
			ctx = internet.ContextWithDialerSource(ctx, h.senderSettings.Via.AsAddress())
		}
		if h.senderSettings != nil {
			ctx = internet.ContextWithStreamSettings(ctx, h.senderSettings.StreamSettings)
		}
	}

	return internet.Dial(ctx, dest)
}

type Connection struct {
	stream     ray.Ray
	closed     bool
	localAddr  net.Addr
	remoteAddr net.Addr

	reader *buf.BufferToBytesReader
	writer *buf.BytesToBufferWriter
}

func NewConnection(stream ray.Ray) *Connection {
	return &Connection{
		stream: stream,
		localAddr: &net.TCPAddr{
			IP:   []byte{0, 0, 0, 0},
			Port: 0,
		},
		remoteAddr: &net.TCPAddr{
			IP:   []byte{0, 0, 0, 0},
			Port: 0,
		},
		reader: buf.NewBytesReader(stream.InboundOutput()),
		writer: buf.NewBytesWriter(stream.InboundInput()),
	}
}

// Read implements net.Conn.Read().
func (v *Connection) Read(b []byte) (int, error) {
	if v.closed {
		return 0, io.EOF
	}
	return v.reader.Read(b)
}

// Write implements net.Conn.Write().
func (v *Connection) Write(b []byte) (int, error) {
	if v.closed {
		return 0, io.ErrClosedPipe
	}
	return v.writer.Write(b)
}

// Close implements net.Conn.Close().
func (v *Connection) Close() error {
	v.closed = true
	v.stream.InboundInput().Close()
	v.stream.InboundOutput().CloseError()
	return nil
}

// LocalAddr implements net.Conn.LocalAddr().
func (v *Connection) LocalAddr() net.Addr {
	return v.localAddr
}

// RemoteAddr implements net.Conn.RemoteAddr().
func (v *Connection) RemoteAddr() net.Addr {
	return v.remoteAddr
}

func (v *Connection) SetDeadline(t time.Time) error {
	return nil
}

func (v *Connection) SetReadDeadline(t time.Time) error {
	return nil
}

func (v *Connection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (v *Connection) Reusable() bool {
	return false
}

func (v *Connection) SetReusable(bool) {

}
<commit_msg>bug fixes in outbound handler<commit_after>package outbound

import (
	"context"
	"errors"
	"io"
	"net"
	"time"

	"v2ray.com/core/app"
	"v2ray.com/core/app/proxyman"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/proxy"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/ray"
)

type Handler struct {
	config          *proxyman.OutboundHandlerConfig
	senderSettings  *proxyman.SenderConfig
	proxy           proxy.Outbound
	outboundManager proxyman.OutboundHandlerManager
}

func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*Handler, error) {
	h := &Handler{
		config: config,
	}
	space := app.SpaceFromContext(ctx)
	if space == nil {
		return nil, errors.New("Proxyman|OutboundHandler: No space in context.")
	}
	space.OnInitialize(func() error {
		ohm := proxyman.OutboundHandlerManagerFromSpace(space)
		if ohm == nil {
			return errors.New("Proxyman|OutboundHandler: No OutboundManager in space.")
		}
		h.outboundManager = ohm
		return nil
	})

	if config.SenderSettings != nil {
		senderSettings, err := config.SenderSettings.GetInstance()
		if err != nil {
			return nil, err
		}
		switch s := senderSettings.(type) {
		case *proxyman.SenderConfig:
			h.senderSettings = s
		default:
			return nil, errors.New("Proxyman|DefaultOutboundHandler: settings is not SenderConfig.")
		}
	}

	proxyHandler, err := config.GetProxyHandler(ctx)
	if err != nil {
		return nil, err
	}

	h.proxy = proxyHandler
	return h, nil
}

func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
	ctx = proxy.ContextWithDialer(ctx, h)
	h.proxy.Process(ctx, outboundRay)
}

func (h *Handler) Dial(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
	if h.senderSettings != nil {
		if h.senderSettings.ProxySettings.HasTag() {
			tag := h.senderSettings.ProxySettings.Tag
			handler := h.outboundManager.GetHandler(tag)
			if handler != nil {
				log.Info("Proxyman|OutboundHandler: Proxying to ", tag)
				ctx = proxy.ContextWithDestination(ctx, dest)
				stream := ray.NewRay(ctx)
				go handler.Dispatch(ctx, stream)
				return NewConnection(stream), nil
			}

			log.Warning("Proxyman|OutboundHandler: Failed to get outbound handler with tag: ", tag)
		}

		if h.senderSettings.Via != nil {
			ctx = internet.ContextWithDialerSource(ctx, h.senderSettings.Via.AsAddress())
		}
		if h.senderSettings != nil {
			ctx = internet.ContextWithStreamSettings(ctx, h.senderSettings.StreamSettings)
		}
	}

	return internet.Dial(ctx, dest)
}

type Connection struct {
	stream     ray.Ray
	closed     bool
	localAddr  net.Addr
	remoteAddr net.Addr

	reader *buf.BufferToBytesReader
	writer *buf.BytesToBufferWriter
}

func NewConnection(stream ray.Ray) *Connection {
	return &Connection{
		stream: stream,
		localAddr: &net.TCPAddr{
			IP:   []byte{0, 0, 0, 0},
			Port: 0,
		},
		remoteAddr: &net.TCPAddr{
			IP:   []byte{0, 0, 0, 0},
			Port: 0,
		},
		reader: buf.NewBytesReader(stream.InboundOutput()),
		writer: buf.NewBytesWriter(stream.InboundInput()),
	}
}

// Read implements net.Conn.Read().
func (v *Connection) Read(b []byte) (int, error) {
	if v.closed {
		return 0, io.EOF
	}
	return v.reader.Read(b)
}

// Write implements net.Conn.Write().
func (v *Connection) Write(b []byte) (int, error) {
	if v.closed {
		return 0, io.ErrClosedPipe
	}
	return v.writer.Write(b)
}

// Close implements net.Conn.Close().
func (v *Connection) Close() error {
	v.closed = true
	v.stream.InboundInput().Close()
	v.stream.InboundOutput().CloseError()
	return nil
}

// LocalAddr implements net.Conn.LocalAddr().
func (v *Connection) LocalAddr() net.Addr {
	return v.localAddr
}

// RemoteAddr implements net.Conn.RemoteAddr().
func (v *Connection) RemoteAddr() net.Addr {
	return v.remoteAddr
}

func (v *Connection) SetDeadline(t time.Time) error {
	return nil
}

func (v *Connection) SetReadDeadline(t time.Time) error {
	return nil
}

func (v *Connection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (v *Connection) Reusable() bool {
	return false
}

func (v *Connection) SetReusable(bool) {

}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 junit describes the test-infra definition of "junit", and provides
// utilities to parse it.
package junit

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
)

// Suites holds a <testsuites/> list of Suite results
type Suites struct {
	XMLName xml.Name `xml:"testsuites"`
	Suites  []Suite  `xml:"testsuite"`
}

// Suite holds <testsuite/> results
type Suite struct {
	XMLName  xml.Name `xml:"testsuite"`
	Name     string   `xml:"name,attr"`
	Time     float64  `xml:"time,attr"` // Seconds
	Failures int      `xml:"failures,attr"`
	Tests    int      `xml:"tests,attr"`
	Results  []Result `xml:"testcase"`
	/*
	* <properties><property name="go.version" value="go1.8.3"/></properties>
	 */
}

// Property defines the xml element that stores additional metrics about each benchmark.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Properties defines the xml element that stores the list of properties that are associated with one benchmark.
type Properties struct {
	PropertyList []Property `xml:"property"`
}

// Result holds <testcase/> results
type Result struct {
	Name       string      `xml:"name,attr"`
	Time       float64     `xml:"time,attr"`
	ClassName  string      `xml:"classname,attr"`
	Failure    *string     `xml:"failure,omitempty"`
	Output     *string     `xml:"system-out,omitempty"`
	Error      *string     `xml:"system-err,omitempty"`
	Skipped    *string     `xml:"skipped,omitempty"`
	Properties *Properties `xml:"properties,omitempty"`
}

// SetProperty adds the specified property to the Result or replaces the
// existing value if a property with that name already exists.
func (r *Result) SetProperty(name, value string) {
	if r.Properties == nil {
		r.Properties = &Properties{}
	}
	for i, existing := range r.Properties.PropertyList {
		if existing.Name == name {
			r.Properties.PropertyList[i].Value = value
			return
		}
	}
	// Didn't find an existing property. Add a new one.
	r.Properties.PropertyList = append(
		r.Properties.PropertyList,
		Property{
			Name:  name,
			Value: value,
		},
	)
}

// Message extracts the message for the junit test case.
//
// Will use the first non-empty <failure/>, <skipped/>, <system-err/>, <system-out/> value.
func (jr Result) Message(max int) string {
	var msg string
	switch {
	case jr.Failure != nil && *jr.Failure != "":
		msg = *jr.Failure
	case jr.Skipped != nil && *jr.Skipped != "":
		msg = *jr.Skipped
	case jr.Error != nil && *jr.Error != "":
		msg = *jr.Error
	case jr.Output != nil && *jr.Output != "":
		msg = *jr.Output
	}
	l := len(msg)
	if max == 0 || l <= max {
		return msg
	}
	h := max / 2
	return msg[:h] + "..." + msg[l-h-1:]
}

func unmarshalXML(buf []byte, i interface{}) error {
	reader := bytes.NewReader(buf)
	dec := xml.NewDecoder(reader)
	dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
		switch charset {
		case "UTF-8", "utf8", "":
			// utf8 is not recognized by golang, but our coalesce.py writes a utf8 doc, which python accepts.
			return input, nil
		default:
			return nil, fmt.Errorf("unknown charset: %s", charset)
		}
	}
	return dec.Decode(i)
}

func Parse(buf []byte) (Suites, error) {
	var suites Suites
	// Try to parse it as a <testsuites/> object
	err := unmarshalXML(buf, &suites)
	if err != nil {
		// Maybe it is a <testsuite/> object instead
		suites.Suites = append([]Suite(nil), Suite{})
		ie := unmarshalXML(buf, &suites.Suites[0])
		if ie != nil {
			// Nope, it just doesn't parse
			return suites, fmt.Errorf("not valid testsuites: %v nor testsuite: %v", err, ie)
		}
	}
	return suites, nil
}
<commit_msg>Allow nested jUnit suites<commit_after>/*
Copyright 2019 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 junit describes the test-infra definition of "junit", and provides
// utilities to parse it.
package junit

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
)

// Suites holds a <testsuites/> list of Suite results
type Suites struct {
	XMLName xml.Name `xml:"testsuites"`
	Suites  []Suite  `xml:"testsuite"`
}

// Suite holds <testsuite/> results
type Suite struct {
	XMLName  xml.Name `xml:"testsuite"`
	Suites   []Suite  `xml:"testsuite"`
	Name     string   `xml:"name,attr"`
	Time     float64  `xml:"time,attr"` // Seconds
	Failures int      `xml:"failures,attr"`
	Tests    int      `xml:"tests,attr"`
	Results  []Result `xml:"testcase"`
	/*
	* <properties><property name="go.version" value="go1.8.3"/></properties>
	 */
}

// Property defines the xml element that stores additional metrics about each benchmark.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Properties defines the xml element that stores the list of properties that are associated with one benchmark.
type Properties struct {
	PropertyList []Property `xml:"property"`
}

// Result holds <testcase/> results
type Result struct {
	Name       string      `xml:"name,attr"`
	Time       float64     `xml:"time,attr"`
	ClassName  string      `xml:"classname,attr"`
	Failure    *string     `xml:"failure,omitempty"`
	Output     *string     `xml:"system-out,omitempty"`
	Error      *string     `xml:"system-err,omitempty"`
	Skipped    *string     `xml:"skipped,omitempty"`
	Properties *Properties `xml:"properties,omitempty"`
}

// SetProperty adds the specified property to the Result or replaces the
// existing value if a property with that name already exists.
func (r *Result) SetProperty(name, value string) {
	if r.Properties == nil {
		r.Properties = &Properties{}
	}
	for i, existing := range r.Properties.PropertyList {
		if existing.Name == name {
			r.Properties.PropertyList[i].Value = value
			return
		}
	}
	// Didn't find an existing property. Add a new one.
	r.Properties.PropertyList = append(
		r.Properties.PropertyList,
		Property{
			Name:  name,
			Value: value,
		},
	)
}

// Message extracts the message for the junit test case.
//
// Will use the first non-empty <failure/>, <skipped/>, <system-err/>, <system-out/> value.
func (jr Result) Message(max int) string {
	var msg string
	switch {
	case jr.Failure != nil && *jr.Failure != "":
		msg = *jr.Failure
	case jr.Skipped != nil && *jr.Skipped != "":
		msg = *jr.Skipped
	case jr.Error != nil && *jr.Error != "":
		msg = *jr.Error
	case jr.Output != nil && *jr.Output != "":
		msg = *jr.Output
	}
	l := len(msg)
	if max == 0 || l <= max {
		return msg
	}
	h := max / 2
	return msg[:h] + "..." + msg[l-h-1:]
}

func unmarshalXML(buf []byte, i interface{}) error {
	reader := bytes.NewReader(buf)
	dec := xml.NewDecoder(reader)
	dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
		switch charset {
		case "UTF-8", "utf8", "":
			// utf8 is not recognized by golang, but our coalesce.py writes a utf8 doc, which python accepts.
			return input, nil
		default:
			return nil, fmt.Errorf("unknown charset: %s", charset)
		}
	}
	return dec.Decode(i)
}

func Parse(buf []byte) (Suites, error) {
	var suites Suites
	// Try to parse it as a <testsuites/> object
	err := unmarshalXML(buf, &suites)
	if err != nil {
		// Maybe it is a <testsuite/> object instead
		suites.Suites = append([]Suite(nil), Suite{})
		ie := unmarshalXML(buf, &suites.Suites[0])
		if ie != nil {
			// Nope, it just doesn't parse
			return suites, fmt.Errorf("not valid testsuites: %v nor testsuite: %v", err, ie)
		}
	}
	return suites, nil
}
<|endoftext|>


<commit_before>package inmemory

import (
	"errors"
	"sync"

	"github.com/Dataman-Cloud/swan/types"
)

var (
	ErrNotExists = errors.New("task does not exist")
)

type Registry struct {
	sync.RWMutex

	tasks map[string]*types.Task
}

func New() *Registry {
	return &Registry{
		tasks: make(map[string]*types.Task),
	}
}

func (r *Registry) Register(id string, task *types.Task) error {
	r.Lock()
	defer r.Unlock()

	r.tasks[id] = task

	return nil
}

func (r *Registry) Fetch(id string) (*types.Task, error) {
	r.RLock()
	defer r.RUnlock()

	t, exists := r.tasks[id]
	if !exists {
		return nil, ErrNotExists
	}

	return t, nil

}

func (r *Registry) Tasks() ([]*types.Task, error) {
	r.RLock()
	defer r.RUnlock()

	var (
		i   int
		out = make([]*types.Task, len(r.tasks))
	)

	for _, v := range r.tasks {
		out[i] = v
		i++
	}

	return out, nil
}

func (r *Registry) Update(id string, t *types.Task) error {
	r.Lock()
	defer r.Unlock()

	r.tasks[id] = t

	return nil
}

func (r *Registry) Delete(id string) error {
	r.Lock()
	defer r.Unlock()

	delete(r.tasks, id)

	return nil
}
<commit_msg>delete inmemory store<commit_after><|endoftext|>


<commit_before>/*
Copyright 2020 The Rook Authors. All rights reserved.

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 cluster

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mgr"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	"github.com/rook/rook/pkg/operator/ceph/cluster/osd"
	"github.com/rook/rook/pkg/operator/ceph/cluster/rbd"
	"github.com/rook/rook/pkg/operator/ceph/controller"
	"github.com/rook/rook/pkg/operator/ceph/file/mds"
	"github.com/rook/rook/pkg/operator/ceph/file/mirror"
	"github.com/rook/rook/pkg/operator/ceph/object"
	"github.com/rook/rook/pkg/operator/k8sutil"
	batch "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
)

const (
	clusterCleanUpPolicyRetryInterval = 5 //seconds
	// CleanupAppName is the cluster clean up job name
	CleanupAppName = "rook-ceph-cleanup"
)

var (
	volumeName                     = "cleanup-volume"
	dataDirHostPath                = "ROOK_DATA_DIR_HOST_PATH"
	namespaceDir                   = "ROOK_NAMESPACE_DIR"
	monitorSecret                  = "ROOK_MON_SECRET"
	clusterFSID                    = "ROOK_CLUSTER_FSID"
	sanitizeMethod                 = "ROOK_SANITIZE_METHOD"
	sanitizeDataSource             = "ROOK_SANITIZE_DATA_SOURCE"
	sanitizeIteration              = "ROOK_SANITIZE_ITERATION"
	sanitizeIterationDefault int32 = 1
)

func (c *ClusterController) startClusterCleanUp(context context.Context, cluster *cephv1.CephCluster, cephHosts []string, monSecret, clusterFSID string) {
	logger.Infof("starting clean up for cluster %q", cluster.Name)
	err := c.waitForCephDaemonCleanUp(context, cluster, time.Duration(clusterCleanUpPolicyRetryInterval)*time.Second)
	if err != nil {
		logger.Errorf("failed to wait till ceph daemons are destroyed. %v", err)
		return
	}

	c.startCleanUpJobs(cluster, cephHosts, monSecret, clusterFSID)
}

func (c *ClusterController) startCleanUpJobs(cluster *cephv1.CephCluster, cephHosts []string, monSecret, clusterFSID string) {
	for _, hostName := range cephHosts {
		logger.Infof("starting clean up job on node %q", hostName)
		jobName := k8sutil.TruncateNodeNameForJob("cluster-cleanup-job-%s", hostName)
		podSpec := c.cleanUpJobTemplateSpec(cluster, monSecret, clusterFSID)
		podSpec.Spec.NodeSelector = map[string]string{v1.LabelHostname: hostName}
		labels := controller.AppLabels(CleanupAppName, cluster.Namespace)
		labels[CleanupAppName] = "true"
		job := &batch.Job{
			ObjectMeta: metav1.ObjectMeta{
				Name:      jobName,
				Namespace: cluster.Namespace,
				Labels:    labels,
			},
			Spec: batch.JobSpec{
				Template: podSpec,
			},
		}

		// Apply annotations
		cephv1.GetCleanupAnnotations(cluster.Spec.Annotations).ApplyToObjectMeta(&job.ObjectMeta)
		cephv1.GetCleanupLabels(cluster.Spec.Labels).ApplyToObjectMeta(&job.ObjectMeta)

		if err := k8sutil.RunReplaceableJob(c.OpManagerCtx, c.context.Clientset, job, true); err != nil {
			logger.Errorf("failed to run cluster clean up job on node %q. %v", hostName, err)
		}
	}
}

func (c *ClusterController) cleanUpJobContainer(cluster *cephv1.CephCluster, monSecret, cephFSID string) v1.Container {
	volumeMounts := []v1.VolumeMount{}
	envVars := []v1.EnvVar{}
	if cluster.Spec.DataDirHostPath != "" {
		if cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration == 0 {
			cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration = sanitizeIterationDefault
		}

		hostPathVolumeMount := v1.VolumeMount{Name: volumeName, MountPath: cluster.Spec.DataDirHostPath}
		devMount := v1.VolumeMount{Name: "devices", MountPath: "/dev"}
		volumeMounts = append(volumeMounts, hostPathVolumeMount)
		volumeMounts = append(volumeMounts, devMount)
		envVars = append(envVars, []v1.EnvVar{
			{Name: dataDirHostPath, Value: cluster.Spec.DataDirHostPath},
			{Name: namespaceDir, Value: cluster.Namespace},
			{Name: monitorSecret, Value: monSecret},
			{Name: clusterFSID, Value: cephFSID},
			{Name: "ROOK_LOG_LEVEL", Value: "DEBUG"},
			mon.PodNamespaceEnvVar(cluster.Namespace),
			{Name: sanitizeMethod, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.Method.String()},
			{Name: sanitizeDataSource, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.DataSource.String()},
			{Name: sanitizeIteration, Value: strconv.Itoa(int(cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration))},
		}...)
	}

	return v1.Container{
		Name:            "host-cleanup",
		Image:           c.rookImage,
		SecurityContext: osd.PrivilegedContext(),
		VolumeMounts:    volumeMounts,
		Env:             envVars,
		Args:            []string{"ceph", "clean"},
		Resources:       cephv1.GetCleanupResources(cluster.Spec.Resources),
	}
}

func (c *ClusterController) cleanUpJobTemplateSpec(cluster *cephv1.CephCluster, monSecret, clusterFSID string) v1.PodTemplateSpec {
	volumes := []v1.Volume{}
	hostPathVolume := v1.Volume{Name: volumeName, VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: cluster.Spec.DataDirHostPath}}}
	devVolume := v1.Volume{Name: "devices", VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/dev"}}}
	volumes = append(volumes, hostPathVolume)
	volumes = append(volumes, devVolume)

	podSpec := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Name: CleanupAppName,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				c.cleanUpJobContainer(cluster, monSecret, clusterFSID),
			},
			Volumes:           volumes,
			RestartPolicy:     v1.RestartPolicyOnFailure,
			PriorityClassName: cephv1.GetCleanupPriorityClassName(cluster.Spec.PriorityClassNames),
		},
	}

	cephv1.GetCleanupAnnotations(cluster.Spec.Annotations).ApplyToObjectMeta(&podSpec.ObjectMeta)
	cephv1.GetCleanupLabels(cluster.Spec.Labels).ApplyToObjectMeta(&podSpec.ObjectMeta)

	// Apply placement
	getCleanupPlacement(cluster.Spec).ApplyToPodSpec(&podSpec.Spec)

	return podSpec
}

// getCleanupPlacement returns the placement for the cleanup job
func getCleanupPlacement(c cephv1.ClusterSpec) cephv1.Placement {
	// The cleanup jobs are assigned by the operator to a specific node, so the
	// node affinity and other affinity are not needed for scheduling.
	// The only placement required for the cleanup daemons is the tolerations.
	tolerations := c.Placement[cephv1.KeyAll].Tolerations
	tolerations = append(tolerations, c.Placement[cephv1.KeyCleanup].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMonArbiter].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMon].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMgr].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyOSD].Tolerations...)

	// Add the tolerations for all the device sets
	for _, deviceSet := range c.Storage.StorageClassDeviceSets {
		tolerations = append(tolerations, deviceSet.Placement.Tolerations...)
	}
	return cephv1.Placement{Tolerations: tolerations}
}

func (c *ClusterController) waitForCephDaemonCleanUp(context context.Context, cluster *cephv1.CephCluster, retryInterval time.Duration) error {
	logger.Infof("waiting for all the ceph daemons to be cleaned up in the cluster %q", cluster.Namespace)
	for {
		select {
		case <-time.After(retryInterval):
			cephHosts, err := c.getCephHosts(cluster.Namespace)
			if err != nil {
				return errors.Wrap(err, "failed to list ceph daemon nodes")
			}

			if len(cephHosts) == 0 {
				logger.Info("all ceph daemons are cleaned up")
				return nil
			}

			logger.Debugf("waiting for ceph daemons in cluster %q to be cleaned up. Retrying in %q",
				cluster.Namespace, retryInterval.String())
		case <-context.Done():
			return errors.Errorf("cancelling the host cleanup job. %s", context.Err())
		}
	}
}

// getCephHosts returns a list of host names where ceph daemon pods are running
func (c *ClusterController) getCephHosts(namespace string) ([]string, error) {
	cephAppNames := []string{mon.AppName, mgr.AppName, osd.AppName, object.AppName, mds.AppName, rbd.AppName, mirror.AppName}
	nodeNameList := sets.NewString()
	hostNameList := []string{}
	var b strings.Builder

	// get all the node names where ceph daemons are running
	for _, app := range cephAppNames {
		appLabelSelector := fmt.Sprintf("app=%s", app)
		podList, err := c.context.Clientset.CoreV1().Pods(namespace).List(c.OpManagerCtx, metav1.ListOptions{LabelSelector: appLabelSelector})
		if err != nil {
			return hostNameList, errors.Wrapf(err, "could not list the %q pods", app)
		}
		for _, cephPod := range podList.Items {
			podNodeName := cephPod.Spec.NodeName
			if podNodeName != "" && !nodeNameList.Has(podNodeName) {
				nodeNameList.Insert(podNodeName)
			}
		}
		fmt.Fprintf(&b, "%s: %d. ", app, len(podList.Items))
	}

	logger.Infof("existing ceph daemons in the namespace %q. %s", namespace, b.String())

	for nodeName := range nodeNameList {
		podHostName, err := k8sutil.GetNodeHostName(c.OpManagerCtx, c.context.Clientset, nodeName)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to get hostname from node %q", nodeName)
		}
		hostNameList = append(hostNameList, podHostName)
	}

	return hostNameList, nil
}

func (c *ClusterController) getCleanUpDetails(namespace string) (string, string, error) {
	clusterInfo, _, _, err := mon.LoadClusterInfo(c.context, c.OpManagerCtx, namespace)
	if err != nil {
		return "", "", errors.Wrap(err, "failed to get cluster info")
	}

	return clusterInfo.MonitorSecret, clusterInfo.FSID, nil
}
<commit_msg>osd: cleanup cluster using the root UID<commit_after>/*
Copyright 2020 The Rook Authors. All rights reserved.

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 cluster

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mgr"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	"github.com/rook/rook/pkg/operator/ceph/cluster/osd"
	"github.com/rook/rook/pkg/operator/ceph/cluster/rbd"
	"github.com/rook/rook/pkg/operator/ceph/controller"
	"github.com/rook/rook/pkg/operator/ceph/file/mds"
	"github.com/rook/rook/pkg/operator/ceph/file/mirror"
	"github.com/rook/rook/pkg/operator/ceph/object"
	"github.com/rook/rook/pkg/operator/k8sutil"
	batch "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
)

const (
	clusterCleanUpPolicyRetryInterval = 5 //seconds
	// CleanupAppName is the cluster clean up job name
	CleanupAppName = "rook-ceph-cleanup"
)

var (
	volumeName                     = "cleanup-volume"
	dataDirHostPath                = "ROOK_DATA_DIR_HOST_PATH"
	namespaceDir                   = "ROOK_NAMESPACE_DIR"
	monitorSecret                  = "ROOK_MON_SECRET"
	clusterFSID                    = "ROOK_CLUSTER_FSID"
	sanitizeMethod                 = "ROOK_SANITIZE_METHOD"
	sanitizeDataSource             = "ROOK_SANITIZE_DATA_SOURCE"
	sanitizeIteration              = "ROOK_SANITIZE_ITERATION"
	sanitizeIterationDefault int32 = 1
)

func (c *ClusterController) startClusterCleanUp(context context.Context, cluster *cephv1.CephCluster, cephHosts []string, monSecret, clusterFSID string) {
	logger.Infof("starting clean up for cluster %q", cluster.Name)
	err := c.waitForCephDaemonCleanUp(context, cluster, time.Duration(clusterCleanUpPolicyRetryInterval)*time.Second)
	if err != nil {
		logger.Errorf("failed to wait till ceph daemons are destroyed. %v", err)
		return
	}

	c.startCleanUpJobs(cluster, cephHosts, monSecret, clusterFSID)
}

func (c *ClusterController) startCleanUpJobs(cluster *cephv1.CephCluster, cephHosts []string, monSecret, clusterFSID string) {
	for _, hostName := range cephHosts {
		logger.Infof("starting clean up job on node %q", hostName)
		jobName := k8sutil.TruncateNodeNameForJob("cluster-cleanup-job-%s", hostName)
		podSpec := c.cleanUpJobTemplateSpec(cluster, monSecret, clusterFSID)
		podSpec.Spec.NodeSelector = map[string]string{v1.LabelHostname: hostName}
		labels := controller.AppLabels(CleanupAppName, cluster.Namespace)
		labels[CleanupAppName] = "true"
		job := &batch.Job{
			ObjectMeta: metav1.ObjectMeta{
				Name:      jobName,
				Namespace: cluster.Namespace,
				Labels:    labels,
			},
			Spec: batch.JobSpec{
				Template: podSpec,
			},
		}

		// Apply annotations
		cephv1.GetCleanupAnnotations(cluster.Spec.Annotations).ApplyToObjectMeta(&job.ObjectMeta)
		cephv1.GetCleanupLabels(cluster.Spec.Labels).ApplyToObjectMeta(&job.ObjectMeta)

		if err := k8sutil.RunReplaceableJob(c.OpManagerCtx, c.context.Clientset, job, true); err != nil {
			logger.Errorf("failed to run cluster clean up job on node %q. %v", hostName, err)
		}
	}
}

func (c *ClusterController) cleanUpJobContainer(cluster *cephv1.CephCluster, monSecret, cephFSID string) v1.Container {
	volumeMounts := []v1.VolumeMount{}
	envVars := []v1.EnvVar{}
	if cluster.Spec.DataDirHostPath != "" {
		if cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration == 0 {
			cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration = sanitizeIterationDefault
		}

		hostPathVolumeMount := v1.VolumeMount{Name: volumeName, MountPath: cluster.Spec.DataDirHostPath}
		devMount := v1.VolumeMount{Name: "devices", MountPath: "/dev"}
		volumeMounts = append(volumeMounts, hostPathVolumeMount)
		volumeMounts = append(volumeMounts, devMount)
		envVars = append(envVars, []v1.EnvVar{
			{Name: dataDirHostPath, Value: cluster.Spec.DataDirHostPath},
			{Name: namespaceDir, Value: cluster.Namespace},
			{Name: monitorSecret, Value: monSecret},
			{Name: clusterFSID, Value: cephFSID},
			{Name: "ROOK_LOG_LEVEL", Value: "DEBUG"},
			mon.PodNamespaceEnvVar(cluster.Namespace),
			{Name: sanitizeMethod, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.Method.String()},
			{Name: sanitizeDataSource, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.DataSource.String()},
			{Name: sanitizeIteration, Value: strconv.Itoa(int(cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration))},
		}...)
	}

	// Run a UID 0 since ceph-volume does not support running non-root
	// See https://tracker.ceph.com/issues/53511
	// Also, it's hard to catch the ceph version since the cluster is being deleted so not
	// implementing a version check and simply always run this as root
	rootUserID := int64(0)
	securityContext := osd.PrivilegedContext()
	securityContext.RunAsUser = &rootUserID

	return v1.Container{
		Name:            "host-cleanup",
		Image:           c.rookImage,
		SecurityContext: securityContext,
		VolumeMounts:    volumeMounts,
		Env:             envVars,
		Args:            []string{"ceph", "clean"},
		Resources:       cephv1.GetCleanupResources(cluster.Spec.Resources),
	}
}

func (c *ClusterController) cleanUpJobTemplateSpec(cluster *cephv1.CephCluster, monSecret, clusterFSID string) v1.PodTemplateSpec {
	volumes := []v1.Volume{}
	hostPathVolume := v1.Volume{Name: volumeName, VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: cluster.Spec.DataDirHostPath}}}
	devVolume := v1.Volume{Name: "devices", VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/dev"}}}
	volumes = append(volumes, hostPathVolume)
	volumes = append(volumes, devVolume)

	podSpec := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Name: CleanupAppName,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				c.cleanUpJobContainer(cluster, monSecret, clusterFSID),
			},
			Volumes:           volumes,
			RestartPolicy:     v1.RestartPolicyOnFailure,
			PriorityClassName: cephv1.GetCleanupPriorityClassName(cluster.Spec.PriorityClassNames),
		},
	}

	cephv1.GetCleanupAnnotations(cluster.Spec.Annotations).ApplyToObjectMeta(&podSpec.ObjectMeta)
	cephv1.GetCleanupLabels(cluster.Spec.Labels).ApplyToObjectMeta(&podSpec.ObjectMeta)

	// Apply placement
	getCleanupPlacement(cluster.Spec).ApplyToPodSpec(&podSpec.Spec)

	return podSpec
}

// getCleanupPlacement returns the placement for the cleanup job
func getCleanupPlacement(c cephv1.ClusterSpec) cephv1.Placement {
	// The cleanup jobs are assigned by the operator to a specific node, so the
	// node affinity and other affinity are not needed for scheduling.
	// The only placement required for the cleanup daemons is the tolerations.
	tolerations := c.Placement[cephv1.KeyAll].Tolerations
	tolerations = append(tolerations, c.Placement[cephv1.KeyCleanup].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMonArbiter].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMon].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyMgr].Tolerations...)
	tolerations = append(tolerations, c.Placement[cephv1.KeyOSD].Tolerations...)

	// Add the tolerations for all the device sets
	for _, deviceSet := range c.Storage.StorageClassDeviceSets {
		tolerations = append(tolerations, deviceSet.Placement.Tolerations...)
	}
	return cephv1.Placement{Tolerations: tolerations}
}

func (c *ClusterController) waitForCephDaemonCleanUp(context context.Context, cluster *cephv1.CephCluster, retryInterval time.Duration) error {
	logger.Infof("waiting for all the ceph daemons to be cleaned up in the cluster %q", cluster.Namespace)
	for {
		select {
		case <-time.After(retryInterval):
			cephHosts, err := c.getCephHosts(cluster.Namespace)
			if err != nil {
				return errors.Wrap(err, "failed to list ceph daemon nodes")
			}

			if len(cephHosts) == 0 {
				logger.Info("all ceph daemons are cleaned up")
				return nil
			}

			logger.Debugf("waiting for ceph daemons in cluster %q to be cleaned up. Retrying in %q",
				cluster.Namespace, retryInterval.String())
		case <-context.Done():
			return errors.Errorf("cancelling the host cleanup job. %s", context.Err())
		}
	}
}

// getCephHosts returns a list of host names where ceph daemon pods are running
func (c *ClusterController) getCephHosts(namespace string) ([]string, error) {
	cephAppNames := []string{mon.AppName, mgr.AppName, osd.AppName, object.AppName, mds.AppName, rbd.AppName, mirror.AppName}
	nodeNameList := sets.NewString()
	hostNameList := []string{}
	var b strings.Builder

	// get all the node names where ceph daemons are running
	for _, app := range cephAppNames {
		appLabelSelector := fmt.Sprintf("app=%s", app)
		podList, err := c.context.Clientset.CoreV1().Pods(namespace).List(c.OpManagerCtx, metav1.ListOptions{LabelSelector: appLabelSelector})
		if err != nil {
			return hostNameList, errors.Wrapf(err, "could not list the %q pods", app)
		}
		for _, cephPod := range podList.Items {
			podNodeName := cephPod.Spec.NodeName
			if podNodeName != "" && !nodeNameList.Has(podNodeName) {
				nodeNameList.Insert(podNodeName)
			}
		}
		fmt.Fprintf(&b, "%s: %d. ", app, len(podList.Items))
	}

	logger.Infof("existing ceph daemons in the namespace %q. %s", namespace, b.String())

	for nodeName := range nodeNameList {
		podHostName, err := k8sutil.GetNodeHostName(c.OpManagerCtx, c.context.Clientset, nodeName)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to get hostname from node %q", nodeName)
		}
		hostNameList = append(hostNameList, podHostName)
	}

	return hostNameList, nil
}

func (c *ClusterController) getCleanUpDetails(namespace string) (string, string, error) {
	clusterInfo, _, _, err := mon.LoadClusterInfo(c.context, c.OpManagerCtx, namespace)
	if err != nil {
		return "", "", errors.Wrap(err, "failed to get cluster info")
	}

	return clusterInfo.MonitorSecret, clusterInfo.FSID, nil
}
<|endoftext|>