<commit_before>package uic

import (
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"log"
	"github.com/Cepave/fe/g"
	"github.com/Cepave/fe/http/base"
	. "github.com/Cepave/fe/model/uic"
	"github.com/Cepave/fe/utils"
	"github.com/toolkits/str"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type AuthController struct {
	base.BaseController
}

func (this *AuthController) Logout() {
	u := this.Ctx.Input.GetData("CurrentUser").(*User)
	RemoveSessionByUid(u.Id)
	this.Ctx.SetCookie("sig", "", 0, "/")
	this.Ctx.SetCookie("sig", "", 0, "/", ".owlemon.com")
	this.Redirect("/auth/login", 302)
}

func (this *AuthController) LoginGet() {
	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")

	cookieSig := this.Ctx.GetCookie("sig")
	if cookieSig == "" {
		this.renderLoginPage(appSig, callback)
		return
	}

	sessionObj := ReadSessionBySig(cookieSig)
	if sessionObj == nil {
		this.renderLoginPage(appSig, callback)
		return
	}

	if int64(sessionObj.Expired) < time.Now().Unix() {
		RemoveSessionByUid(sessionObj.Uid)
		this.renderLoginPage(appSig, callback)
		return
	}

	if appSig != "" && callback != "" {
		this.Redirect(callback, 302)
	} else {
		this.Redirect("/me/info", 302)
	}
}

func (this *AuthController) LoginPost() {
	name := this.GetString("name", "")
	password := this.GetString("password", "")

	if name == "" || password == "" {
		this.ServeErrJson("name or password is blank")
		return
	}

	var u *User

	ldapEnabled := this.MustGetBool("ldap", false)

	if ldapEnabled {
		sucess, err := utils.LdapBind(g.Config().Ldap.Addr,
			g.Config().Ldap.BaseDN,
			g.Config().Ldap.BindDN,
			g.Config().Ldap.BindPasswd,
			g.Config().Ldap.UserField,
			name,
			password)
		if err != nil {
			this.ServeErrJson(err.Error())
			return
		}

		if !sucess {
			this.ServeErrJson("name or password error")
			return
		}

		user_attributes, err := utils.Ldapsearch(g.Config().Ldap.Addr,
			g.Config().Ldap.BaseDN,
			g.Config().Ldap.BindDN,
			g.Config().Ldap.BindPasswd,
			g.Config().Ldap.UserField,
			name,
			g.Config().Ldap.Attributes)
		userSn := ""
		userMail := ""
		userTel := ""
		if err == nil {
			userSn = user_attributes["sn"]
			userMail = user_attributes["mail"]
			userTel = user_attributes["telephoneNumber"]
		}

		arr := strings.Split(name, "@")
		var userName, userEmail string
		if len(arr) == 2 {
			userName = arr[0]
			userEmail = name
		} else {
			userName = name
			userEmail = userMail
		}

		u = ReadUserByName(userName)
		if u == nil {
			// 说明用户不存在
			u = &User{
				Name:   userName,
				Passwd: "",
				Cnname: userSn,
				Phone:  userTel,
				Email:  userEmail,
			}
			_, err = u.Save()
			if err != nil {
				this.ServeErrJson("insert user fail " + err.Error())
				return
			}
		}
	} else {
		u = ReadUserByName(name)
		if u == nil {
			this.ServeErrJson("no such user")
			return
		}

		if u.Passwd != str.Md5Encode(g.Config().Salt+password) {
			this.ServeErrJson("password error")
			return
		}
	}

	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")
	if appSig != "" && callback != "" {
		SaveSessionAttrs(u.Id, appSig, int(time.Now().Unix())+3600*24*30)
	} else {
		this.CreateSession(u.Id, 3600*24*30)
	}

	this.ServeDataJson(callback)
}

func (this *AuthController) renderLoginPage(sig, callback string) {
	this.Data["CanRegister"] = g.Config().CanRegister
	this.Data["LdapEnabled"] = g.Config().Ldap.Enabled
	this.Data["Sig"] = sig
	this.Data["Callback"] = callback
	this.Data["Shortcut"] = g.Config().Shortcut
	this.TplNames = "auth/login.html"
}

func (this *AuthController) RegisterGet() {
	this.Data["CanRegister"] = g.Config().CanRegister
	this.Data["Shortcut"] = g.Config().Shortcut
	this.TplNames = "auth/register.html"
}

func (this *AuthController) RegisterPost() {
	if !g.Config().CanRegister {
		this.ServeErrJson("registration system is not open")
		return
	}

	name := strings.TrimSpace(this.GetString("name", ""))
	password := strings.TrimSpace(this.GetString("password", ""))
	repeatPassword := strings.TrimSpace(this.GetString("repeat_password", ""))

	if password != repeatPassword {
		this.ServeErrJson("password not equal the repeart one")
		return
	}

	if !utils.IsUsernameValid(name) {
		this.ServeErrJson("name pattern is invalid")
		return
	}

	if ReadUserIdByName(name) > 0 {
		this.ServeErrJson("name is already existent")
		return
	}

	lastId, err := InsertRegisterUser(name, str.Md5Encode(g.Config().Salt+password))
	if err != nil {
		this.ServeErrJson("insert user fail " + err.Error())
		return
	}

	this.CreateSession(lastId, 3600*24*30)

	this.ServeOKJson()
}

func (this *AuthController) CreateSession(uid int64, maxAge int) int {
	sig := utils.GenerateUUID()
	expired := int(time.Now().Unix()) + maxAge
	SaveSessionAttrs(uid, sig, expired)
	this.Ctx.SetCookie("sig", sig, maxAge, "/")
	this.Ctx.SetCookie("sig", sig, maxAge, "/", ".owlemon.com")
	return expired
}

/**
 * @function name:   func (this *AuthController) LoginThirdParty()
 * @description:     This function returns third party login URL.
 * @related issues:  OWL-206
 * @param:           void
 * @return:          void
 * @author:          Don Hsieh
 * @since:           12/17/2015
 * @last modified:   12/17/2015
 * @called by:       beego.Router("/auth/third-party", &AuthController{}, "post:LoginThirdParty")
 *                    in fe/http/uic/uic_routes.go
 */
func (this *AuthController) LoginThirdParty() {
	s := g.Config().Api.Redirect
	s = base64.StdEncoding.EncodeToString([]byte(s))
	strEncoded := url.QueryEscape(s)
	loginUrl := g.Config().Api.Login + "/" + strEncoded
	this.ServeDataJson(loginUrl)
}

/**
 * @function name:   func getRequest(url string) map[string]interface{}
 * @description:     This function sends GET request to given URL.
 * @related issues:  OWL-206, OWL-159
 * @param:           url string
 * @return:          map[string]interface{}
 * @author:          Don Hsieh
 * @since:           12/17/2015
 * @last modified:   12/17/2015
 * @called by:       func (this *AuthController) LoginWithToken()
 *                    in fe/http/uic/auth_controller.go
 */
func getRequest(url string) map[string]interface{} {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Println("Error =", err.Error())
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Error =", err.Error())
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	var nodes = make(map[string]interface{})
	if err := json.Unmarshal(body, &nodes); err != nil {
		log.Println("Error =", err.Error())
	}
	return nodes
}

/**
 * @function name:   func (this *AuthController) LoginWithToken()
 * @description:     This function logins user with third party token.
 * @related issues:  OWL-206
 * @param:           void
 * @return:          void
 * @author:          Don Hsieh
 * @since:           12/16/2015
 * @last modified:   12/17/2015
 * @called by:       beego.Router("/auth/login/:token", &AuthController{}, "get:LoginWithToken")
 *                    in fe/http/uic/uic_routes.go
 */
func (this *AuthController) LoginWithToken() {
	token := this.Ctx.Input.Param(":token")
	key := g.Config().Api.Key
	authUrl := g.Config().Api.Access + "/" + token + "/" + key

	nodes := getRequest(authUrl)
	if status, ok := nodes["status"]; ok {
		if int(status.(float64)) == 1 {
			data := nodes["data"].(map[string]interface {})
			access_key := data["access_key"].(string)
			username := data["username"].(string)
			email := data["email"].(string)
			log.Println("access_key =", access_key)

			urlRole := g.Config().Api.Role + "/" + access_key
			nodes := getRequest(urlRole)
			role := 3
			if int(nodes["status"].(float64)) == 1 {
				permission := nodes["data"]
				log.Println("permission =", permission)
				if permission == "admin" {
					role = 0
				} else if permission == "operator" {
					role = 1
				} else if permission == "observer" {
					role = 2
				} else if permission == "deny" {
					role = 3
				}
			}
			user := ReadUserByName(username)
			if user == nil {		// create third party user
				InsertRegisterUser(username, "")
				user = ReadUserByName(username)
			}
			user.Passwd = ""
			user.Email = email
			user.Role = role
			user.Update()
			appSig := this.GetString("sig", "")
			callback := this.GetString("callback", "")
			if appSig != "" && callback != "" {
				SaveSessionAttrs(user.Id, appSig, int(time.Now().Unix())+3600*24*30)
			} else {
				this.CreateSession(user.Id, 3600*24*30)
			}
			maxAge := 3600*24*30
			this.Ctx.SetCookie("token", token, maxAge, "/")
			this.Ctx.SetCookie("token", token, maxAge, "/", ".owlemon.com")
			this.Redirect("/me/info", 302)
		}
	}
	// not logged in. redirect to login page.
	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")
	this.renderLoginPage(appSig, callback)
}
<commit_msg>[OWL-186] add third party log out<commit_after>package uic

import (
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"log"
	"github.com/Cepave/fe/g"
	"github.com/Cepave/fe/http/base"
	. "github.com/Cepave/fe/model/uic"
	"github.com/Cepave/fe/utils"
	"github.com/toolkits/str"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type AuthController struct {
	base.BaseController
}

func (this *AuthController) Logout() {
	u := this.Ctx.Input.GetData("CurrentUser").(*User)
	token := this.Ctx.GetCookie("token")
	if len(token) > 0 {
		url := g.Config().Api.Logout + "/" + token
		log.Println("logout url =", url)
		result := getRequest(url)
		log.Println("logout result =", result)
		this.Ctx.SetCookie("token", "", 0, "/")
		this.Ctx.SetCookie("token", "", 0, "/", ".owlemon.com")
	}
	RemoveSessionByUid(u.Id)
	this.Ctx.SetCookie("sig", "", 0, "/")
	this.Ctx.SetCookie("sig", "", 0, "/", ".owlemon.com")
	this.Redirect("/auth/login", 302)
}

func (this *AuthController) LoginGet() {
	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")

	cookieSig := this.Ctx.GetCookie("sig")
	if cookieSig == "" {
		this.renderLoginPage(appSig, callback)
		return
	}

	sessionObj := ReadSessionBySig(cookieSig)
	if sessionObj == nil {
		this.renderLoginPage(appSig, callback)
		return
	}

	if int64(sessionObj.Expired) < time.Now().Unix() {
		RemoveSessionByUid(sessionObj.Uid)
		this.renderLoginPage(appSig, callback)
		return
	}

	if appSig != "" && callback != "" {
		this.Redirect(callback, 302)
	} else {
		this.Redirect("/me/info", 302)
	}
}

func (this *AuthController) LoginPost() {
	name := this.GetString("name", "")
	password := this.GetString("password", "")

	if name == "" || password == "" {
		this.ServeErrJson("name or password is blank")
		return
	}

	var u *User

	ldapEnabled := this.MustGetBool("ldap", false)

	if ldapEnabled {
		sucess, err := utils.LdapBind(g.Config().Ldap.Addr,
			g.Config().Ldap.BaseDN,
			g.Config().Ldap.BindDN,
			g.Config().Ldap.BindPasswd,
			g.Config().Ldap.UserField,
			name,
			password)
		if err != nil {
			this.ServeErrJson(err.Error())
			return
		}

		if !sucess {
			this.ServeErrJson("name or password error")
			return
		}

		user_attributes, err := utils.Ldapsearch(g.Config().Ldap.Addr,
			g.Config().Ldap.BaseDN,
			g.Config().Ldap.BindDN,
			g.Config().Ldap.BindPasswd,
			g.Config().Ldap.UserField,
			name,
			g.Config().Ldap.Attributes)
		userSn := ""
		userMail := ""
		userTel := ""
		if err == nil {
			userSn = user_attributes["sn"]
			userMail = user_attributes["mail"]
			userTel = user_attributes["telephoneNumber"]
		}

		arr := strings.Split(name, "@")
		var userName, userEmail string
		if len(arr) == 2 {
			userName = arr[0]
			userEmail = name
		} else {
			userName = name
			userEmail = userMail
		}

		u = ReadUserByName(userName)
		if u == nil {
			// 说明用户不存在
			u = &User{
				Name:   userName,
				Passwd: "",
				Cnname: userSn,
				Phone:  userTel,
				Email:  userEmail,
			}
			_, err = u.Save()
			if err != nil {
				this.ServeErrJson("insert user fail " + err.Error())
				return
			}
		}
	} else {
		u = ReadUserByName(name)
		if u == nil {
			this.ServeErrJson("no such user")
			return
		}

		if u.Passwd != str.Md5Encode(g.Config().Salt+password) {
			this.ServeErrJson("password error")
			return
		}
	}

	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")
	if appSig != "" && callback != "" {
		SaveSessionAttrs(u.Id, appSig, int(time.Now().Unix())+3600*24*30)
	} else {
		this.CreateSession(u.Id, 3600*24*30)
	}

	this.ServeDataJson(callback)
}

func (this *AuthController) renderLoginPage(sig, callback string) {
	this.Data["CanRegister"] = g.Config().CanRegister
	this.Data["LdapEnabled"] = g.Config().Ldap.Enabled
	this.Data["Sig"] = sig
	this.Data["Callback"] = callback
	this.Data["Shortcut"] = g.Config().Shortcut
	this.TplNames = "auth/login.html"
}

func (this *AuthController) RegisterGet() {
	this.Data["CanRegister"] = g.Config().CanRegister
	this.Data["Shortcut"] = g.Config().Shortcut
	this.TplNames = "auth/register.html"
}

func (this *AuthController) RegisterPost() {
	if !g.Config().CanRegister {
		this.ServeErrJson("registration system is not open")
		return
	}

	name := strings.TrimSpace(this.GetString("name", ""))
	password := strings.TrimSpace(this.GetString("password", ""))
	repeatPassword := strings.TrimSpace(this.GetString("repeat_password", ""))

	if password != repeatPassword {
		this.ServeErrJson("password not equal the repeart one")
		return
	}

	if !utils.IsUsernameValid(name) {
		this.ServeErrJson("name pattern is invalid")
		return
	}

	if ReadUserIdByName(name) > 0 {
		this.ServeErrJson("name is already existent")
		return
	}

	lastId, err := InsertRegisterUser(name, str.Md5Encode(g.Config().Salt+password))
	if err != nil {
		this.ServeErrJson("insert user fail " + err.Error())
		return
	}

	this.CreateSession(lastId, 3600*24*30)

	this.ServeOKJson()
}

func (this *AuthController) CreateSession(uid int64, maxAge int) int {
	sig := utils.GenerateUUID()
	expired := int(time.Now().Unix()) + maxAge
	SaveSessionAttrs(uid, sig, expired)
	this.Ctx.SetCookie("sig", sig, maxAge, "/")
	this.Ctx.SetCookie("sig", sig, maxAge, "/", ".owlemon.com")
	return expired
}

/**
 * @function name:   func (this *AuthController) LoginThirdParty()
 * @description:     This function returns third party login URL.
 * @related issues:  OWL-206
 * @param:           void
 * @return:          void
 * @author:          Don Hsieh
 * @since:           12/17/2015
 * @last modified:   12/17/2015
 * @called by:       beego.Router("/auth/third-party", &AuthController{}, "post:LoginThirdParty")
 *                    in fe/http/uic/uic_routes.go
 */
func (this *AuthController) LoginThirdParty() {
	s := g.Config().Api.Redirect
	s = base64.StdEncoding.EncodeToString([]byte(s))
	strEncoded := url.QueryEscape(s)
	loginUrl := g.Config().Api.Login + "/" + strEncoded
	this.ServeDataJson(loginUrl)
}

/**
 * @function name:   func getRequest(url string) map[string]interface{}
 * @description:     This function sends GET request to given URL.
 * @related issues:  OWL-206, OWL-159
 * @param:           url string
 * @return:          map[string]interface{}
 * @author:          Don Hsieh
 * @since:           12/17/2015
 * @last modified:   12/17/2015
 * @called by:       func (this *AuthController) LoginWithToken()
 *                    in fe/http/uic/auth_controller.go
 */
func getRequest(url string) map[string]interface{} {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Println("Error =", err.Error())
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Error =", err.Error())
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	var nodes = make(map[string]interface{})
	if err := json.Unmarshal(body, &nodes); err != nil {
		log.Println("Error =", err.Error())
	}
	return nodes
}

/**
 * @function name:   func (this *AuthController) LoginWithToken()
 * @description:     This function logins user with third party token.
 * @related issues:  OWL-206
 * @param:           void
 * @return:          void
 * @author:          Don Hsieh
 * @since:           12/16/2015
 * @last modified:   12/17/2015
 * @called by:       beego.Router("/auth/login/:token", &AuthController{}, "get:LoginWithToken")
 *                    in fe/http/uic/uic_routes.go
 */
func (this *AuthController) LoginWithToken() {
	token := this.Ctx.Input.Param(":token")
	key := g.Config().Api.Key
	authUrl := g.Config().Api.Access + "/" + token + "/" + key

	nodes := getRequest(authUrl)
	if status, ok := nodes["status"]; ok {
		if int(status.(float64)) == 1 {
			data := nodes["data"].(map[string]interface {})
			access_key := data["access_key"].(string)
			username := data["username"].(string)
			email := data["email"].(string)
			log.Println("access_key =", access_key)

			urlRole := g.Config().Api.Role + "/" + access_key
			nodes := getRequest(urlRole)
			role := 3
			if int(nodes["status"].(float64)) == 1 {
				permission := nodes["data"]
				log.Println("permission =", permission)
				if permission == "admin" {
					role = 0
				} else if permission == "operator" {
					role = 1
				} else if permission == "observer" {
					role = 2
				} else if permission == "deny" {
					role = 3
				}
			}
			user := ReadUserByName(username)
			if user == nil {		// create third party user
				InsertRegisterUser(username, "")
				user = ReadUserByName(username)
			}
			user.Passwd = ""
			user.Email = email
			user.Role = role
			user.Update()
			appSig := this.GetString("sig", "")
			callback := this.GetString("callback", "")
			if appSig != "" && callback != "" {
				SaveSessionAttrs(user.Id, appSig, int(time.Now().Unix())+3600*24*30)
			} else {
				this.CreateSession(user.Id, 3600*24*30)
			}
			maxAge := 3600*24*30
			this.Ctx.SetCookie("token", token, maxAge, "/")
			this.Ctx.SetCookie("token", token, maxAge, "/", ".owlemon.com")
			this.Redirect("/me/info", 302)
		}
	}
	// not logged in. redirect to login page.
	appSig := this.GetString("sig", "")
	callback := this.GetString("callback", "")
	this.renderLoginPage(appSig, callback)
}
<|endoftext|>


<commit_before>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package api

import (
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced"
	coordclient "github.com/zenoss/serviced/coordinator/client"
	coordzk "github.com/zenoss/serviced/coordinator/client/zookeeper"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/dao/elasticsearch"
	"github.com/zenoss/serviced/datastore"
	"github.com/zenoss/serviced/datastore/elastic"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/pool"
	"github.com/zenoss/serviced/facade"
	"github.com/zenoss/serviced/isvcs"
	"github.com/zenoss/serviced/proxy"
	"github.com/zenoss/serviced/rpc/agent"
	"github.com/zenoss/serviced/rpc/master"
	"github.com/zenoss/serviced/scheduler"
	"github.com/zenoss/serviced/shell"
	"github.com/zenoss/serviced/stats"
	"github.com/zenoss/serviced/utils"
	"github.com/zenoss/serviced/volume"
	// Need to do btrfs driver initializations
	_ "github.com/zenoss/serviced/volume/btrfs"
	// Need to do rsync driver initializations
	_ "github.com/zenoss/serviced/volume/rsync"
	"github.com/zenoss/serviced/web"

	"errors"
	"fmt"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"path"
	"syscall"
	"time"
)

var minDockerVersion = version{0, 8, 1}

type daemon struct {
	staticIPs []string
	cpDao     dao.ControlPlane
	dsDriver  datastore.Driver
	dsContext datastore.Context
	facade    *facade.Facade
	hostID    string
	zclient   *coordclient.Client
}

func newDaemon(staticIPs []string) (*daemon, error) {
	d := &daemon{
		staticIPs: staticIPs,
	}
	return d, nil
}

func (d *daemon) run() error {
	var err error
	d.hostID, err = utils.HostID()
	if err != nil {
		glog.Fatalf("could not get hostid: %s", err)
	}

	l, err := net.Listen("tcp", options.Listen)
	if err != nil {
		glog.Fatalf("Could not bind to port %v. Is another instance running", err)
	}

	//This asserts isvcs
	//TODO: should this just be in startMaster
	isvcs.Init()
	isvcs.Mgr.SetVolumesDir(path.Join(options.VarPath, "isvcs"))

	dockerVersion, err := serviced.GetDockerVersion()
	if err != nil {
		glog.Fatalf("could not determine docker version: %s", err)
	}

	if minDockerVersion.Compare(dockerVersion.Client) < 0 {
		glog.Fatalf("serviced needs at least docker >= 0.8.1")
	}

	//TODO: is this needed for both agent and master?
	if _, ok := volume.Registered(options.VFS); !ok {
		glog.Fatalf("no driver registered for %s", options.VFS)
	}

	if options.Master {
		if err = d.startMaster(); err != nil {
			glog.Fatalf("%v", err)
		}
	}
	if options.Agent {
		if _, err = d.startAgent(); err != nil {
			glog.Fatalf("%v", err)
		}
	}

	rpc.HandleHTTP()

	if options.ReportStats {
		statsdest := fmt.Sprintf("http://%s/api/metrics/store", options.HostStats)
		statsduration := time.Duration(options.StatsPeriod) * time.Second
		glog.V(1).Infoln("Staring container statistics reporter")
		statsReporter := stats.NewStatsReporter(statsdest, statsduration)
		defer statsReporter.Close()
	}

	glog.V(0).Infof("Listening on %s", l.Addr().String())
	return http.Serve(l, nil) // start the server
}

func (d *daemon) initContext() (datastore.Context, error) {
	datastore.Register(d.dsDriver)
	ctx := datastore.Get()
	if ctx == nil {
		return nil, errors.New("context not available")
	}
	return ctx, nil
}

func (d *daemon) startMaster() error {
	if err := d.initISVCS(); err != nil {
		return err
	}

	var err error
	if d.dsDriver, err = d.initDriver(); err != nil {
		return err
	}

	if d.dsContext, err = d.initContext(); err != nil {
		return err
	}

	d.facade = d.initFacade()

	if d.zclient, err = d.initZK(); err != nil {
		return err
	}

	if d.cpDao, err = d.initDAO(); err != nil {
		return err
	}

	if err = d.facade.CreateDefaultPool(d.dsContext); err != nil {
		return err
	}

	if err = d.registerMasterRPC(); err != nil {
		return err
	}

	d.initWeb()

	d.startScheduler()
	return nil
}

func (d *daemon) startAgent() (hostAgent *serviced.HostAgent, err error) {
	mux := proxy.TCPMux{}

	mux.CertPEMFile = options.CertPEMFile
	mux.KeyPEMFile = options.KeyPEMFile
	mux.Enabled = true
	mux.Port = options.MuxPort
	mux.UseTLS = options.TLS

	hostAgent, err = serviced.NewHostAgent(options.Port, options.UIPort, options.DockerDNS, options.VarPath, options.Mount, options.VFS, options.Zookeepers, mux)
	if err != nil {
		glog.Fatalf("Could not start ControlPlane agent: %v", err)
	}
	// register the API
	glog.V(0).Infoln("registering ControlPlaneAgent service")
	if err = rpc.RegisterName("ControlPlaneAgent", hostAgent); err != nil {
		glog.Fatalf("could not register ControlPlaneAgent RPC server: %v", err)
	}
	glog.Infof("agent start staticips: %v [%d]", d.staticIPs, len(d.staticIPs))
	if err = rpc.RegisterName("Agent", agent.NewServer(d.staticIPs)); err != nil {
		glog.Fatalf("could not register Agent RPC server: %v", err)
	}

	go func() {
		signalChan := make(chan os.Signal, 10)
		signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
		<-signalChan
		glog.V(0).Info("Shutting down due to interrupt")
		err = hostAgent.Shutdown()
		if err != nil {
			glog.V(1).Infof("Agent shutdown with error: %v", err)
		} else {
			glog.Info("Agent shutdown")
		}
		isvcs.Mgr.Stop()
		os.Exit(0)
	}()

	// TODO: Integrate this server into the rpc server, or something.
	// Currently its only use is for command execution.
	go func() {
		sio := shell.NewProcessExecutorServer(options.Port)
		http.ListenAndServe(":50000", sio)
	}()
	return hostAgent, nil
}

func (d *daemon) registerMasterRPC() error {
	glog.V(0).Infoln("registering Master RPC services")

	if err := rpc.RegisterName("Master", master.NewServer()); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}

	// register the deprecated rpc servers
	if err := rpc.RegisterName("LoadBalancer", d.cpDao); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}

	if err := rpc.RegisterName("ControlPlane", d.cpDao); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}
	return nil
}
func (d *daemon) initDriver() (datastore.Driver, error) {

	//TODO: figure out elastic mappings
	eDriver := elastic.New("localhost", 9200, "controlplane")
	eDriver.AddMapping(host.MAPPING)
	eDriver.AddMapping(pool.MAPPING)
	err := eDriver.Initialize(10 * time.Second)
	if err != nil {
		return nil, err
	}
	return eDriver, nil

}

func (d *daemon) initFacade() *facade.Facade {
	f := facade.New()
	return f
}

func (d *daemon) initISVCS() error {
	return isvcs.Mgr.Start()
}

func (d *daemon) initZK() (*coordclient.Client, error) {
	dsn := coordzk.NewDSN(options.Zookeepers, time.Second*15).String()
	glog.Infof("zookeeper dsn: %s", dsn)
	zclient, err := coordclient.New("zookeeper", dsn, "", nil)
	return zclient, err
}

func (d *daemon) initDAO() (dao.ControlPlane, error) {
	return elasticsearch.NewControlSvc("localhost", 9200, d.facade, d.zclient, options.VarPath, options.VFS)
}

func (d *daemon) initWeb() {
	// TODO: Make bind port for web server optional?
	glog.V(4).Infof("Starting web server: uiport: %v; port: %v; zookeepers: %v", options.UIPort, options.Port, options.Zookeepers)
	cpserver := web.NewServiceConfig(options.UIPort, options.Port, options.Zookeepers, options.ReportStats, options.HostAliases)
	go cpserver.ServeUI()
	go cpserver.Serve()

}
func (d *daemon) startScheduler() {
	go d.runScheduler()
}

func (d *daemon) runScheduler() {
	for {
		func() {
			conn, err := d.zclient.GetConnection()
			if err != nil {
				return
			}
			defer conn.Close()

			sched, shutdown := scheduler.NewScheduler("", conn, d.hostID, d.cpDao, d.facade)
			sched.Start()
			select {
			case <-shutdown:
			}
		}()
	}

}
<commit_msg>bumping min docker version<commit_after>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package api

import (
	"github.com/zenoss/glog"
	"github.com/zenoss/serviced"
	coordclient "github.com/zenoss/serviced/coordinator/client"
	coordzk "github.com/zenoss/serviced/coordinator/client/zookeeper"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/dao/elasticsearch"
	"github.com/zenoss/serviced/datastore"
	"github.com/zenoss/serviced/datastore/elastic"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/pool"
	"github.com/zenoss/serviced/facade"
	"github.com/zenoss/serviced/isvcs"
	"github.com/zenoss/serviced/proxy"
	"github.com/zenoss/serviced/rpc/agent"
	"github.com/zenoss/serviced/rpc/master"
	"github.com/zenoss/serviced/scheduler"
	"github.com/zenoss/serviced/shell"
	"github.com/zenoss/serviced/stats"
	"github.com/zenoss/serviced/utils"
	"github.com/zenoss/serviced/volume"
	// Need to do btrfs driver initializations
	_ "github.com/zenoss/serviced/volume/btrfs"
	// Need to do rsync driver initializations
	_ "github.com/zenoss/serviced/volume/rsync"
	"github.com/zenoss/serviced/web"

	"errors"
	"fmt"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"path"
	"syscall"
	"time"
)

var minDockerVersion = version{0, 10, 0}

type daemon struct {
	staticIPs []string
	cpDao     dao.ControlPlane
	dsDriver  datastore.Driver
	dsContext datastore.Context
	facade    *facade.Facade
	hostID    string
	zclient   *coordclient.Client
}

func newDaemon(staticIPs []string) (*daemon, error) {
	d := &daemon{
		staticIPs: staticIPs,
	}
	return d, nil
}

func (d *daemon) run() error {
	var err error
	d.hostID, err = utils.HostID()
	if err != nil {
		glog.Fatalf("could not get hostid: %s", err)
	}

	l, err := net.Listen("tcp", options.Listen)
	if err != nil {
		glog.Fatalf("Could not bind to port %v. Is another instance running", err)
	}

	//This asserts isvcs
	//TODO: should this just be in startMaster
	isvcs.Init()
	isvcs.Mgr.SetVolumesDir(path.Join(options.VarPath, "isvcs"))

	dockerVersion, err := serviced.GetDockerVersion()
	if err != nil {
		glog.Fatalf("could not determine docker version: %s", err)
	}

	if minDockerVersion.Compare(dockerVersion.Client) < 0 {
		glog.Fatalf("serviced needs at least docker >= %s", minDockerVersion)
	}

	//TODO: is this needed for both agent and master?
	if _, ok := volume.Registered(options.VFS); !ok {
		glog.Fatalf("no driver registered for %s", options.VFS)
	}

	if options.Master {
		if err = d.startMaster(); err != nil {
			glog.Fatalf("%v", err)
		}
	}
	if options.Agent {
		if _, err = d.startAgent(); err != nil {
			glog.Fatalf("%v", err)
		}
	}

	rpc.HandleHTTP()

	if options.ReportStats {
		statsdest := fmt.Sprintf("http://%s/api/metrics/store", options.HostStats)
		statsduration := time.Duration(options.StatsPeriod) * time.Second
		glog.V(1).Infoln("Staring container statistics reporter")
		statsReporter := stats.NewStatsReporter(statsdest, statsduration)
		defer statsReporter.Close()
	}

	glog.V(0).Infof("Listening on %s", l.Addr().String())
	return http.Serve(l, nil) // start the server
}

func (d *daemon) initContext() (datastore.Context, error) {
	datastore.Register(d.dsDriver)
	ctx := datastore.Get()
	if ctx == nil {
		return nil, errors.New("context not available")
	}
	return ctx, nil
}

func (d *daemon) startMaster() error {
	if err := d.initISVCS(); err != nil {
		return err
	}

	var err error
	if d.dsDriver, err = d.initDriver(); err != nil {
		return err
	}

	if d.dsContext, err = d.initContext(); err != nil {
		return err
	}

	d.facade = d.initFacade()

	if d.zclient, err = d.initZK(); err != nil {
		return err
	}

	if d.cpDao, err = d.initDAO(); err != nil {
		return err
	}

	if err = d.facade.CreateDefaultPool(d.dsContext); err != nil {
		return err
	}

	if err = d.registerMasterRPC(); err != nil {
		return err
	}

	d.initWeb()

	d.startScheduler()
	return nil
}

func (d *daemon) startAgent() (hostAgent *serviced.HostAgent, err error) {
	mux := proxy.TCPMux{}

	mux.CertPEMFile = options.CertPEMFile
	mux.KeyPEMFile = options.KeyPEMFile
	mux.Enabled = true
	mux.Port = options.MuxPort
	mux.UseTLS = options.TLS

	hostAgent, err = serviced.NewHostAgent(options.Port, options.UIPort, options.DockerDNS, options.VarPath, options.Mount, options.VFS, options.Zookeepers, mux)
	if err != nil {
		glog.Fatalf("Could not start ControlPlane agent: %v", err)
	}
	// register the API
	glog.V(0).Infoln("registering ControlPlaneAgent service")
	if err = rpc.RegisterName("ControlPlaneAgent", hostAgent); err != nil {
		glog.Fatalf("could not register ControlPlaneAgent RPC server: %v", err)
	}
	glog.Infof("agent start staticips: %v [%d]", d.staticIPs, len(d.staticIPs))
	if err = rpc.RegisterName("Agent", agent.NewServer(d.staticIPs)); err != nil {
		glog.Fatalf("could not register Agent RPC server: %v", err)
	}

	go func() {
		signalChan := make(chan os.Signal, 10)
		signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
		<-signalChan
		glog.V(0).Info("Shutting down due to interrupt")
		err = hostAgent.Shutdown()
		if err != nil {
			glog.V(1).Infof("Agent shutdown with error: %v", err)
		} else {
			glog.Info("Agent shutdown")
		}
		isvcs.Mgr.Stop()
		os.Exit(0)
	}()

	// TODO: Integrate this server into the rpc server, or something.
	// Currently its only use is for command execution.
	go func() {
		sio := shell.NewProcessExecutorServer(options.Port)
		http.ListenAndServe(":50000", sio)
	}()
	return hostAgent, nil
}

func (d *daemon) registerMasterRPC() error {
	glog.V(0).Infoln("registering Master RPC services")

	if err := rpc.RegisterName("Master", master.NewServer()); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}

	// register the deprecated rpc servers
	if err := rpc.RegisterName("LoadBalancer", d.cpDao); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}

	if err := rpc.RegisterName("ControlPlane", d.cpDao); err != nil {
		return fmt.Errorf("could not register rpc server LoadBalancer: %v", err)
	}
	return nil
}
func (d *daemon) initDriver() (datastore.Driver, error) {

	//TODO: figure out elastic mappings
	eDriver := elastic.New("localhost", 9200, "controlplane")
	eDriver.AddMapping(host.MAPPING)
	eDriver.AddMapping(pool.MAPPING)
	err := eDriver.Initialize(10 * time.Second)
	if err != nil {
		return nil, err
	}
	return eDriver, nil

}

func (d *daemon) initFacade() *facade.Facade {
	f := facade.New()
	return f
}

func (d *daemon) initISVCS() error {
	return isvcs.Mgr.Start()
}

func (d *daemon) initZK() (*coordclient.Client, error) {
	dsn := coordzk.NewDSN(options.Zookeepers, time.Second*15).String()
	glog.Infof("zookeeper dsn: %s", dsn)
	zclient, err := coordclient.New("zookeeper", dsn, "", nil)
	return zclient, err
}

func (d *daemon) initDAO() (dao.ControlPlane, error) {
	return elasticsearch.NewControlSvc("localhost", 9200, d.facade, d.zclient, options.VarPath, options.VFS)
}

func (d *daemon) initWeb() {
	// TODO: Make bind port for web server optional?
	glog.V(4).Infof("Starting web server: uiport: %v; port: %v; zookeepers: %v", options.UIPort, options.Port, options.Zookeepers)
	cpserver := web.NewServiceConfig(options.UIPort, options.Port, options.Zookeepers, options.ReportStats, options.HostAliases)
	go cpserver.ServeUI()
	go cpserver.Serve()

}
func (d *daemon) startScheduler() {
	go d.runScheduler()
}

func (d *daemon) runScheduler() {
	for {
		func() {
			conn, err := d.zclient.GetConnection()
			if err != nil {
				return
			}
			defer conn.Close()

			sched, shutdown := scheduler.NewScheduler("", conn, d.hostID, d.cpDao, d.facade)
			sched.Start()
			select {
			case <-shutdown:
			}
		}()
	}

}
<|endoftext|>


<commit_before>package cli_test

import (
	"bytes"
	"fmt"
	"github.com/qadium/plumber/shell"
	"github.com/qadium/plumber/cli"
	"net/http"
	"testing"
	"syscall"
	"time"
)

func createTestBundleAndPipeline(t *testing.T, ctx *cli.Context, pipeline, bundleName, tempDir string) {
	// create a pipeline
	if err := ctx.Create(pipeline); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: error creating '%v'", err)
	}

	// make a usable bundle and bundle it
	createTestBundle(t, bundleName, tempDir)
	if err := ctx.Bundle(tempDir); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: error bundling test bundle, '%v'", err)
	}

	// add that bundle to the pipeline
	if err := ctx.Add(pipeline, tempDir); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: '%v'", err)
	}

	// bootstrap the manager
	if err := ctx.Bootstrap(); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: Got an error during bootstrap: '%v'", err)
	}
}

// Tests the Start command.
func TestStart(t *testing.T) {
	ctx, tempDir := NewTestContext(t)
	defer cleanTestDir(t, tempDir)

	const testPipeline = "test-start"
	const testBundle = "bazbux"
	createTestBundleAndPipeline(t, ctx, testPipeline, testBundle, tempDir)
	defer shell.RunAndLog("docker", "rmi", ctx.GetImage(testBundle))
	defer shell.RunAndLog("docker", "rmi", ctx.GetManagerImage())

	// set the interrupt handler to go off after 5 seconds
	go func() {
		time.Sleep(5 * time.Second)
		syscall.Kill(syscall.Getpid(), syscall.SIGINT)
	}()

	// send a post request to the "server" and see what we get back
	go func() {
		time.Sleep(4 * time.Second)
		hostIp, err := ctx.GetDockerHost()
		if err != nil {
			t.Errorf("TestStart: Got an error getting the docker host: '%v'", err)
		}
		resp, err := http.Post(fmt.Sprintf("http://%s:9800", hostIp), "application/json", bytes.NewBufferString(`{"a": "trusty"}`))
		if err != nil {
			t.Error(err)
		}
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		result := buf.String()
		if result != `{"a": "trusty", "b": "echo trusty"}` {
			t.Errorf("TestStart: Got '%s'; did not get expected response", result)
		}
	}()

	// start the pipeline locally (set the gce project to '' to run
	// locally)
	err := ctx.Start(testPipeline, "")
	if err != nil {
		t.Errorf("TestStart: '%v'", err)
	}

	// now attempt to start it remotely
	const projectId = "gce-project-id"
	err = ctx.Start(testPipeline, projectId)
	if err != nil {
		t.Errorf("hmmmmmmm '%v'", err)
	}
	remoteImage := fmt.Sprintf("gcr.io/%s/plumber-%s", projectId, "manager")
	defer shell.RunAndLog("docker", "rmi", remoteImage)
	remoteImage = fmt.Sprintf("gcr.io/%s/plumber-%s", projectId, testBundle)
	defer shell.RunAndLog("docker", "rmi", remoteImage)
}

func TestStartNonExistentPipeline(t *testing.T) {
	ctx, tempDir := NewTestContext(t)
	defer cleanTestDir(t, tempDir)

	err := ctx.Start("", "")
	if err == nil || err.Error() != fmt.Sprintf("stat %s/: no such file or directory", ctx.PipeDir) {
		t.Errorf("TestStartNonExistentPipeline: did not get expected error '%s', '%v'", ctx.PipeDir, err)
	}
}
<commit_msg>updated start test<commit_after>package cli_test

import (
	"bytes"
	"fmt"
	"github.com/qadium/plumber/shell"
	"github.com/qadium/plumber/cli"
	"net/http"
	"testing"
	"syscall"
	"time"
)

func createTestBundleAndPipeline(t *testing.T, ctx *cli.Context, pipeline, bundleName, tempDir string) {
	// create a pipeline
	if err := ctx.Create(pipeline); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: error creating '%v'", err)
	}

	// make a usable bundle and bundle it
	createTestBundle(t, bundleName, tempDir)
	if err := ctx.Bundle(tempDir); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: error bundling test bundle, '%v'", err)
	}

	// add that bundle to the pipeline
	if err := ctx.Add(pipeline, tempDir); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: '%v'", err)
	}

	// bootstrap the manager
	if err := ctx.Bootstrap(); err != nil {
		t.Errorf("CreateTestBundleAndPipeline: Got an error during bootstrap: '%v'", err)
	}
}

// Tests the Start command.
func TestStart(t *testing.T) {
	ctx, tempDir := NewTestContext(t)
	defer cleanTestDir(t, tempDir)

	const testPipeline = "test-start"
	const testBundle = "bazbux"
	createTestBundleAndPipeline(t, ctx, testPipeline, testBundle, tempDir)
	defer shell.RunAndLog("docker", "rmi", ctx.GetImage(testBundle))
	defer shell.RunAndLog("docker", "rmi", ctx.GetManagerImage())

	// set the interrupt handler to go off after 5 seconds
	go func() {
		time.Sleep(5 * time.Second)
		syscall.Kill(syscall.Getpid(), syscall.SIGINT)
	}()

	// send a post request to the "server" and see what we get back
	go func() {
		time.Sleep(4 * time.Second)
		hostIp, err := ctx.GetDockerHost()
		if err != nil {
			t.Errorf("TestStart: Got an error getting the docker host: '%v'", err)
		}
		resp, err := http.Post(fmt.Sprintf("http://%s:9800", hostIp), "application/json", bytes.NewBufferString(`{"a": "trusty"}`))
		if err != nil {
			t.Error(err)
		}
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		result := buf.String()
		if result != `{"a": "trusty", "b": "echo trusty"}` {
			t.Errorf("TestStart: Got '%s'; did not get expected response", result)
		}
	}()

	// start the pipeline locally (set the gce project to '' to run
	// locally)
	err := ctx.Start(testPipeline, "")
	if err != nil {
		t.Errorf("TestStart: '%v'", err)
	}

	// now attempt to start it remotely
	const projectId = "gce-project-id"
	err = ctx.Start(testPipeline, projectId)
	if err != nil {
		t.Errorf("TestStart: [remote] '%v'", err)
	}
	remoteImage := fmt.Sprintf("gcr.io/%s/plumber-%s", projectId, "manager")
	defer shell.RunAndLog("docker", "rmi", remoteImage)
	remoteImage = fmt.Sprintf("gcr.io/%s/plumber-%s", projectId, testBundle)
	defer shell.RunAndLog("docker", "rmi", remoteImage)
}

func TestStartNonExistentPipeline(t *testing.T) {
	ctx, tempDir := NewTestContext(t)
	defer cleanTestDir(t, tempDir)

	err := ctx.Start("", "")
	if err == nil || err.Error() != fmt.Sprintf("stat %s/: no such file or directory", ctx.PipeDir) {
		t.Errorf("TestStartNonExistentPipeline: did not get expected error, '%v'", err)
	}
}
<|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 priority

import (
	"flag"
	"sort"
	"time"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
	"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
	vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
	"k8s.io/klog"
)

const (
	// Ignore change priority that is smaller than 10%.
	defaultUpdateThreshold = 0.10
	// Pods that live for at least that long can be evicted even if their
	// request is within the [MinRecommended...MaxRecommended] range.
	podLifetimeUpdateThreshold = time.Hour * 12
)

var (
	evictAfterOOMThreshold = flag.Duration("evict-after-oom-threshold", 10*time.Minute,
		`Evict pod that has only one container and it OOMed in less than
		evict-after-oom-threshold since start.`)
)

// UpdatePriorityCalculator is responsible for prioritizing updates on pods.
// It can returns a sorted list of pods in order of update priority.
// Update priority is proportional to fraction by which resources should be increased / decreased.
// i.e. pod with 10M current memory and recommendation 20M will have higher update priority
// than pod with 100M current memory and 150M recommendation (100% increase vs 50% increase)
type UpdatePriorityCalculator struct {
	vpa                     *vpa_types.VerticalPodAutoscaler
	pods                    []prioritizedPod
	config                  *UpdateConfig
	recommendationProcessor vpa_api_util.RecommendationProcessor
	priorityProcessor       PriorityProcessor
}

// UpdateConfig holds configuration for UpdatePriorityCalculator
type UpdateConfig struct {
	// MinChangePriority is the minimum change priority that will trigger a update.
	// TODO: should have separate for Mem and CPU?
	MinChangePriority float64
}

// NewUpdatePriorityCalculator creates new UpdatePriorityCalculator for the given VPA object
// an update config.
// If the vpa resource policy is nil, there will be no policy restriction on update.
// If the given update config is nil, default values are used.
func NewUpdatePriorityCalculator(vpa *vpa_types.VerticalPodAutoscaler,
	config *UpdateConfig,
	recommendationProcessor vpa_api_util.RecommendationProcessor,
	priorityProcessor PriorityProcessor) UpdatePriorityCalculator {
	if config == nil {
		config = &UpdateConfig{MinChangePriority: defaultUpdateThreshold}
	}
	return UpdatePriorityCalculator{
		vpa:                     vpa,
		config:                  config,
		recommendationProcessor: recommendationProcessor,
		priorityProcessor:       priorityProcessor}
}

// AddPod adds pod to the UpdatePriorityCalculator.
func (calc *UpdatePriorityCalculator) AddPod(pod *apiv1.Pod, now time.Time) {
	processedRecommendation, _, err := calc.recommendationProcessor.Apply(calc.vpa.Status.Recommendation, calc.vpa.Spec.ResourcePolicy, calc.vpa.Status.Conditions, pod)
	if err != nil {
		klog.V(2).Infof("cannot process recommendation for pod %s/%s: %v", pod.Namespace, pod.Name, err)
		return
	}

	hasObservedContainers, vpaContainerSet := parseVpaObservedContainers(pod)

	updatePriority := calc.priorityProcessor.GetUpdatePriority(pod, calc.vpa, processedRecommendation)

	quickOOM := false
	for i := range pod.Status.ContainerStatuses {
		cs := &pod.Status.ContainerStatuses[i]
		if hasObservedContainers && !vpaContainerSet.Has(cs.Name) {
			// Containers not observed by Admission Controller are not supported
			// by the quick OOM logic.
			klog.V(4).Infof("Not listed in %s:%s. Skipping container %s quick OOM calculations",
				annotations.VpaObservedContainersLabel, pod.GetAnnotations()[annotations.VpaObservedContainersLabel], cs.Name)
			continue
		}
		crp := vpa_api_util.GetContainerResourcePolicy(cs.Name, calc.vpa.Spec.ResourcePolicy)
		if crp != nil && crp.Mode != nil && *crp.Mode == vpa_types.ContainerScalingModeOff {
			// Containers with ContainerScalingModeOff are not considered
			// during the quick OOM calculation.
			klog.V(4).Infof("Container with ContainerScalingModeOff. Skipping container %s quick OOM calculations", cs.Name)
			continue
		}
		terminationState := &cs.LastTerminationState
		if terminationState.Terminated != nil &&
			terminationState.Terminated.Reason == "OOMKilled" &&
			terminationState.Terminated.FinishedAt.Time.Sub(terminationState.Terminated.StartedAt.Time) < *evictAfterOOMThreshold {
			quickOOM = true
			klog.V(2).Infof("quick OOM detected in pod %v/%v, container %v", pod.Namespace, pod.Name, cs.Name)
		}
	}

	// The update is allowed in following cases:
	// - the request is outside the recommended range for some container.
	// - the pod lives for at least 24h and the resource diff is >= MinChangePriority.
	// - a vpa scaled container OOMed in less than evictAfterOOMThreshold.
	if !updatePriority.OutsideRecommendedRange && !quickOOM {
		if pod.Status.StartTime == nil {
			// TODO: Set proper condition on the VPA.
			klog.V(2).Infof("not updating pod %v/%v, missing field pod.Status.StartTime", pod.Namespace, pod.Name)
			return
		}
		if now.Before(pod.Status.StartTime.Add(podLifetimeUpdateThreshold)) {
			klog.V(2).Infof("not updating a short-lived pod %v/%v, request within recommended range", pod.Namespace, pod.Name)
			return
		}
		if updatePriority.ResourceDiff < calc.config.MinChangePriority {
			klog.V(2).Infof("not updating pod %v/%v, resource diff too low: %v", pod.Namespace, pod.Name, updatePriority)
			return
		}
	}

	// If the pod has quick OOMed then evict only if the resources will change
	if quickOOM && updatePriority.ResourceDiff == 0 {
		klog.V(2).Infof("not updating pod %v/%v because resource would not change", pod.Namespace, pod.Name)
		return
	}
	klog.V(2).Infof("pod accepted for update %v/%v with priority %v", pod.Namespace, pod.Name, updatePriority.ResourceDiff)
	calc.pods = append(calc.pods, prioritizedPod{
		pod:            pod,
		priority:       updatePriority,
		recommendation: processedRecommendation})
}

// GetSortedPods returns a list of pods ordered by update priority (highest update priority first)
func (calc *UpdatePriorityCalculator) GetSortedPods(admission PodEvictionAdmission) []*apiv1.Pod {
	sort.Sort(byPriorityDesc(calc.pods))

	result := []*apiv1.Pod{}
	for _, podPrio := range calc.pods {
		if admission == nil || admission.Admit(podPrio.pod, podPrio.recommendation) {
			result = append(result, podPrio.pod)
		} else {
			klog.V(2).Infof("pod removed from update queue by PodEvictionAdmission: %v", podPrio.pod.Name)
		}
	}

	return result
}

func parseVpaObservedContainers(pod *apiv1.Pod) (bool, sets.String) {
	observedContainers, hasObservedContainers := pod.GetAnnotations()[annotations.VpaObservedContainersLabel]
	vpaContainerSet := sets.NewString()
	if hasObservedContainers {
		if containers, err := annotations.ParseVpaObservedContainersValue(observedContainers); err != nil {
			klog.Errorf("Vpa annotation %s failed to parse: %v", observedContainers, err)
			hasObservedContainers = false
		} else {
			vpaContainerSet.Insert(containers...)
		}
	}
	return hasObservedContainers, vpaContainerSet
}

type prioritizedPod struct {
	pod            *apiv1.Pod
	priority       PodPriority
	recommendation *vpa_types.RecommendedPodResources
}

// PodPriority contains data for a pod update that can be used to prioritize between updates.
type PodPriority struct {
	// Is any container outside of the recommended range.
	OutsideRecommendedRange bool
	// Does any container want to grow.
	ScaleUp bool
	// Relative difference between the total requested and total recommended resources.
	ResourceDiff float64
}

type byPriorityDesc []prioritizedPod

func (list byPriorityDesc) Len() int {
	return len(list)
}
func (list byPriorityDesc) Swap(i, j int) {
	list[i], list[j] = list[j], list[i]
}

// Less implements reverse ordering by priority (highest priority first).
// This means we return true if priority at index j is lower than at index i.
func (list byPriorityDesc) Less(i, j int) bool {
	return list[j].priority.Less(list[i].priority)
}

// Less returns true if p is lower than other.
func (p PodPriority) Less(other PodPriority) bool {
	// 1. If any container wants to grow, the pod takes precedence.
	// TODO: A better policy would be to prioritize scaling down when
	// (a) the pod is pending
	// (b) there is general resource shortage
	// and prioritize scaling up otherwise.
	if p.ScaleUp != other.ScaleUp {
		return other.ScaleUp
	}
	// 2. A pod with larger value of resourceDiff takes precedence.
	return p.ResourceDiff < other.ResourceDiff
}
<commit_msg>allow configure pod lifetime thresholds<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 priority

import (
	"flag"
	"sort"
	"time"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
	"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
	vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
	"k8s.io/klog"
)

var (
	defaultUpdateThreshold = flag.Float64("pod-update-threshold", 0.1, "Ignore updates that have priority lower than the value of this flag")

	podLifetimeUpdateThreshold = flag.Duration("in-recommendation-bounds-eviction-lifetime-threshold", time.Hour*12, "Pods that live for at least that long can be evicted even if their request is within the [MinRecommended...MaxRecommended] range")

	evictAfterOOMThreshold = flag.Duration("evict-after-oom-threshold", 10*time.Minute,
		`Evict pod that has only one container and it OOMed in less than
		evict-after-oom-threshold since start.`)
)

// UpdatePriorityCalculator is responsible for prioritizing updates on pods.
// It can returns a sorted list of pods in order of update priority.
// Update priority is proportional to fraction by which resources should be increased / decreased.
// i.e. pod with 10M current memory and recommendation 20M will have higher update priority
// than pod with 100M current memory and 150M recommendation (100% increase vs 50% increase)
type UpdatePriorityCalculator struct {
	vpa                     *vpa_types.VerticalPodAutoscaler
	pods                    []prioritizedPod
	config                  *UpdateConfig
	recommendationProcessor vpa_api_util.RecommendationProcessor
	priorityProcessor       PriorityProcessor
}

// UpdateConfig holds configuration for UpdatePriorityCalculator
type UpdateConfig struct {
	// MinChangePriority is the minimum change priority that will trigger a update.
	// TODO: should have separate for Mem and CPU?
	MinChangePriority float64
}

// NewUpdatePriorityCalculator creates new UpdatePriorityCalculator for the given VPA object
// an update config.
// If the vpa resource policy is nil, there will be no policy restriction on update.
// If the given update config is nil, default values are used.
func NewUpdatePriorityCalculator(vpa *vpa_types.VerticalPodAutoscaler,
	config *UpdateConfig,
	recommendationProcessor vpa_api_util.RecommendationProcessor,
	priorityProcessor PriorityProcessor) UpdatePriorityCalculator {
	if config == nil {
		config = &UpdateConfig{MinChangePriority: *defaultUpdateThreshold}
	}
	return UpdatePriorityCalculator{
		vpa:                     vpa,
		config:                  config,
		recommendationProcessor: recommendationProcessor,
		priorityProcessor:       priorityProcessor}
}

// AddPod adds pod to the UpdatePriorityCalculator.
func (calc *UpdatePriorityCalculator) AddPod(pod *apiv1.Pod, now time.Time) {
	processedRecommendation, _, err := calc.recommendationProcessor.Apply(calc.vpa.Status.Recommendation, calc.vpa.Spec.ResourcePolicy, calc.vpa.Status.Conditions, pod)
	if err != nil {
		klog.V(2).Infof("cannot process recommendation for pod %s/%s: %v", pod.Namespace, pod.Name, err)
		return
	}

	hasObservedContainers, vpaContainerSet := parseVpaObservedContainers(pod)

	updatePriority := calc.priorityProcessor.GetUpdatePriority(pod, calc.vpa, processedRecommendation)

	quickOOM := false
	for i := range pod.Status.ContainerStatuses {
		cs := &pod.Status.ContainerStatuses[i]
		if hasObservedContainers && !vpaContainerSet.Has(cs.Name) {
			// Containers not observed by Admission Controller are not supported
			// by the quick OOM logic.
			klog.V(4).Infof("Not listed in %s:%s. Skipping container %s quick OOM calculations",
				annotations.VpaObservedContainersLabel, pod.GetAnnotations()[annotations.VpaObservedContainersLabel], cs.Name)
			continue
		}
		crp := vpa_api_util.GetContainerResourcePolicy(cs.Name, calc.vpa.Spec.ResourcePolicy)
		if crp != nil && crp.Mode != nil && *crp.Mode == vpa_types.ContainerScalingModeOff {
			// Containers with ContainerScalingModeOff are not considered
			// during the quick OOM calculation.
			klog.V(4).Infof("Container with ContainerScalingModeOff. Skipping container %s quick OOM calculations", cs.Name)
			continue
		}
		terminationState := &cs.LastTerminationState
		if terminationState.Terminated != nil &&
			terminationState.Terminated.Reason == "OOMKilled" &&
			terminationState.Terminated.FinishedAt.Time.Sub(terminationState.Terminated.StartedAt.Time) < *evictAfterOOMThreshold {
			quickOOM = true
			klog.V(2).Infof("quick OOM detected in pod %v/%v, container %v", pod.Namespace, pod.Name, cs.Name)
		}
	}

	// The update is allowed in following cases:
	// - the request is outside the recommended range for some container.
	// - the pod lives for at least 24h and the resource diff is >= MinChangePriority.
	// - a vpa scaled container OOMed in less than evictAfterOOMThreshold.
	if !updatePriority.OutsideRecommendedRange && !quickOOM {
		if pod.Status.StartTime == nil {
			// TODO: Set proper condition on the VPA.
			klog.V(2).Infof("not updating pod %v/%v, missing field pod.Status.StartTime", pod.Namespace, pod.Name)
			return
		}
		if now.Before(pod.Status.StartTime.Add(*podLifetimeUpdateThreshold)) {
			klog.V(2).Infof("not updating a short-lived pod %v/%v, request within recommended range", pod.Namespace, pod.Name)
			return
		}
		if updatePriority.ResourceDiff < calc.config.MinChangePriority {
			klog.V(2).Infof("not updating pod %v/%v, resource diff too low: %v", pod.Namespace, pod.Name, updatePriority)
			return
		}
	}

	// If the pod has quick OOMed then evict only if the resources will change
	if quickOOM && updatePriority.ResourceDiff == 0 {
		klog.V(2).Infof("not updating pod %v/%v because resource would not change", pod.Namespace, pod.Name)
		return
	}
	klog.V(2).Infof("pod accepted for update %v/%v with priority %v", pod.Namespace, pod.Name, updatePriority.ResourceDiff)
	calc.pods = append(calc.pods, prioritizedPod{
		pod:            pod,
		priority:       updatePriority,
		recommendation: processedRecommendation})
}

// GetSortedPods returns a list of pods ordered by update priority (highest update priority first)
func (calc *UpdatePriorityCalculator) GetSortedPods(admission PodEvictionAdmission) []*apiv1.Pod {
	sort.Sort(byPriorityDesc(calc.pods))

	result := []*apiv1.Pod{}
	for _, podPrio := range calc.pods {
		if admission == nil || admission.Admit(podPrio.pod, podPrio.recommendation) {
			result = append(result, podPrio.pod)
		} else {
			klog.V(2).Infof("pod removed from update queue by PodEvictionAdmission: %v", podPrio.pod.Name)
		}
	}

	return result
}

func parseVpaObservedContainers(pod *apiv1.Pod) (bool, sets.String) {
	observedContainers, hasObservedContainers := pod.GetAnnotations()[annotations.VpaObservedContainersLabel]
	vpaContainerSet := sets.NewString()
	if hasObservedContainers {
		if containers, err := annotations.ParseVpaObservedContainersValue(observedContainers); err != nil {
			klog.Errorf("Vpa annotation %s failed to parse: %v", observedContainers, err)
			hasObservedContainers = false
		} else {
			vpaContainerSet.Insert(containers...)
		}
	}
	return hasObservedContainers, vpaContainerSet
}

type prioritizedPod struct {
	pod            *apiv1.Pod
	priority       PodPriority
	recommendation *vpa_types.RecommendedPodResources
}

// PodPriority contains data for a pod update that can be used to prioritize between updates.
type PodPriority struct {
	// Is any container outside of the recommended range.
	OutsideRecommendedRange bool
	// Does any container want to grow.
	ScaleUp bool
	// Relative difference between the total requested and total recommended resources.
	ResourceDiff float64
}

type byPriorityDesc []prioritizedPod

func (list byPriorityDesc) Len() int {
	return len(list)
}
func (list byPriorityDesc) Swap(i, j int) {
	list[i], list[j] = list[j], list[i]
}

// Less implements reverse ordering by priority (highest priority first).
// This means we return true if priority at index j is lower than at index i.
func (list byPriorityDesc) Less(i, j int) bool {
	return list[j].priority.Less(list[i].priority)
}

// Less returns true if p is lower than other.
func (p PodPriority) Less(other PodPriority) bool {
	// 1. If any container wants to grow, the pod takes precedence.
	// TODO: A better policy would be to prioritize scaling down when
	// (a) the pod is pending
	// (b) there is general resource shortage
	// and prioritize scaling up otherwise.
	if p.ScaleUp != other.ScaleUp {
		return other.ScaleUp
	}
	// 2. A pod with larger value of resourceDiff takes precedence.
	return p.ResourceDiff < other.ResourceDiff
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"github.com/APTrust/exchange/util/fileutil"
	"github.com/APTrust/exchange/util/partner"
	"os"
	"strings"
)

type Options struct {
	// PathToConfigFile is the path the APTrust partner config
	// file. If not specified, this defaults to ~/.aptrust_partner.conf.
	// This can be omitted entirely if you supply the -bucket and -key
	// options on the command line. Any required options not specified
	// on the command line will be pulled from this file.
	PathToConfigFile string
	// AccessKeyId is your AWS access key id. Used for authentication.
	AccessKeyId string
	// AccessKeyFrom describes the source from which the Options object
	// loaded the AWS AccessKeyId. This is used only for testing and debugging.
	AccessKeyFrom string
	// APTrustAPIKey is the key to connect to APTrust REST API.
	// The key must belong to APTrustAPIUser.
	APTrustAPIKey string
	// APTrustAPIKeyFrom tells whether the API key came from the config
	// file or the environment.
	APTrustAPIKeyFrom string
	// APTrustAPIKey is the user email address to connect to APTrust REST API.
	APTrustAPIUser string
	// APTrustAPIUserFrom tells whether the API user came from the config
	// file or the environment.
	APTrustAPIUserFrom string
	// SecretAccessKey is the AWS Secret Access Key used to access your
	// S3 bucket.
	SecretAccessKey string
	// SecretKeyFrom describes the source from which the Options object
	// loaded the AWS SecretAccessKey. This is used only for testing and
	// debugging.
	SecretKeyFrom string
	// Region is the AWS S3 region to connect to.
	Region string
	// Bucket is the name of the bucket you're working with.
	Bucket string
	// Key is the name of the S3 key to download, list, or delete.
	Key string
	// Dir is the directory into which the S3 object should be downloaded.
	// This option is for downloads only.
	Dir string
	// ContentType is the content type of the object being uploaded
	// to S3. This option applies to uploads only, and can be left
	// empty.
	ContentType string
	// Metadata is optional metadata to be saved in S3 when uploading
	// a file.
	Metadata map[string]string
	// FileToUpload is the path the file that should be uploaded to S3.
	// This is required for apt_upload only, and is ignored elsewhere.
	FileToUpload string
	// PharosURL is the URL of the Pharos production or demo system.
	PharosURL string
	// OutputFormat specifies how the program should print its results
	// to STDOUT. Options are "text" and "json".
	OutputFormat string
	// Debug indicates whether we should print debug output to Stdout.
	Debug bool
	// error contains a list of errors describing why these options are
	// not valid for an operation like upload or download.
	errors []string
}

// SetAndVerifyDownloadOptions tries to fill in options
// that were not supplied on the command line with those
// specified in the APTrust partner config file. It also
// verifies that all required and allowed values are present.
// Check opts.HasErrors() after calling this, to see if we
// have sufficient options info to proceed with a download.
func (opts *Options) SetAndVerifyDownloadOptions() {
	opts.ClearErrors()
	if opts.OutputFormat == "" {
		opts.OutputFormat = "text"
	}
	opts.MergeConfigFileOptions()
	opts.VerifyOutputFormat()
	opts.EnsureDownloadDirIsSet()
	opts.VerifyRequiredDownloadOptions()
}

// SetAndVerifyUploadOptions
func (opts *Options) SetAndVerifyUploadOptions() {
	opts.ClearErrors()
	if opts.OutputFormat == "" {
		opts.OutputFormat = "text"
	}
	opts.MergeConfigFileOptions()
	opts.VerifyOutputFormat()
	opts.VerifyRequiredUploadOptions()
}

// VerifyRequiredDownloadOptions checks to see that all
// required download options are set.
func (opts *Options) VerifyRequiredDownloadOptions() {
	if opts.Key == "" {
		opts.addError("Param -key must be specified on the command line")
	}
	if opts.Bucket == "" {
		opts.addError("Param -bucket must be specified on the command line or in the config file")
	}
	if opts.AccessKeyId == "" {
		opts.addError("Cannot find AWS_ACCESS_KEY_ID in environment or config file")
	}
	if opts.SecretAccessKey == "" {
		opts.addError("Cannot find AWS_SECRET_ACCESS_KEY in environment or config file")
	}
}

// VerifyRequiredUploadOptions checks to see that all
// required upload options are set.
func (opts *Options) VerifyRequiredUploadOptions() {
	if opts.Bucket == "" {
		opts.addError("Param -bucket must be specified on the command line or in the config file")
	}
	if opts.AccessKeyId == "" {
		opts.addError("Cannot find AWS_ACCESS_KEY_ID in environment or config file")
	}
	if opts.SecretAccessKey == "" {
		opts.addError("Cannot find AWS_SECRET_ACCESS_KEY in environment or config file")
	}
	if opts.FileToUpload == "" {
		opts.addError("You must specify a file to upload")
	}
}

// VerifyOutputFormat makes sure the user specified a valid output format.
func (opts *Options) VerifyOutputFormat() {
	if opts.OutputFormat != "text" && opts.OutputFormat != "json" {
		opts.addError("Param -format must be either 'text' or 'json'")
	}
}

func (opts *Options) VerifyRequiredAPICredentials() {
	if opts.APTrustAPIUser == "" {
		opts.addError("Cannot find APTrust API user in environment or config file")
	}
	if opts.APTrustAPIKey == "" {
		opts.addError("Cannot find APTrust API key in environment or config file")
	}
}

// EnsureDownloadDirIsSet makes sure we have a directory to download the file into.
func (opts *Options) EnsureDownloadDirIsSet() {
	var err error
	// If the dir setting has a tilde, expand it to the user's
	// home directory. This call fails if the system cannot
	// determine the user.
	dir, _ := fileutil.ExpandTilde(opts.Dir)
	if dir == "" {
		dir = opts.Dir
	}
	if dir == "" {
		dir, err = os.Getwd()
		if err != nil {
			dir, err = fileutil.RelativeToAbsPath(".")
			if err != nil {
				dir = "."
			}
		}
	}
	opts.Dir = dir
}

// MergeConfigFileOptions supplements command-line options with
// the default values the user specified in their APTrust
// parner config file.
//
// If the user left some options unspecified on the command line,
// load them from the config file, if we can. If the user specified
// a config file, use that. Otherwise, use the default config file
// in ~/.aptrust_partner.conf or %HOMEPATH%\.aptrust_partner.conf
func (opts *Options) MergeConfigFileOptions() {
	partnerConfig := &PartnerConfig{}
	if opts.PathToConfigFile != "" && partner.DefaultConfigFileExists() {
		var err error
		partnerConfig, err = opts.LoadConfigFile()
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
	}
	if opts.Bucket == "" && partnerConfig.RestorationBucket != "" {
		opts.Bucket = partnerConfig.RestorationBucket
	}
	if opts.Dir == "" && partnerConfig.DownloadDir != "" {
		opts.Dir = partnerConfig.DownloadDir
	}
	if opts.AccessKeyId == "" {
		if partnerConfig.AwsAccessKeyId != "" {
			opts.AccessKeyId = partnerConfig.AwsAccessKeyId
			opts.AccessKeyFrom = opts.PathToConfigFile
		} else {
			opts.AccessKeyId = os.Getenv("AWS_ACCESS_KEY_ID")
			opts.AccessKeyFrom = "ENV['AWS_ACCESS_KEY_ID']"
		}
	}
	if opts.SecretAccessKey == "" {
		if partnerConfig.AwsSecretAccessKey != "" {
			opts.SecretAccessKey = partnerConfig.AwsSecretAccessKey
			opts.AccessKeyFrom = opts.PathToConfigFile
		} else {
			opts.SecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
			opts.SecretKeyFrom = "ENV['AWS_SECRET_ACCESS_KEY']"
		}
	}
	if opts.APTrustAPIKey == "" {
		if partnerConfig.APTrustAPIKey != "" {
			opts.APTrustAPIKey = partnerConfig.APTrustAPIKey
			opts.APTrustAPIKeyFrom = opts.PathToConfigFile
		} else if os.Getenv("APTRUST_API_KEY") != "" {
			opts.APTrustAPIKey = os.Getenv("APTRUST_API_KEY")
			opts.APTrustAPIKeyFrom = "ENV['APTRUST_API_KEY']"
		} else if os.Getenv("PHAROS_API_KEY") != "" {
			opts.APTrustAPIKey = os.Getenv("PHAROS_API_KEY")
			opts.APTrustAPIKeyFrom = "ENV['PHAROS_API_KEY']"
		}
	}
	if opts.APTrustAPIUser == "" {
		if partnerConfig.APTrustAPIUser != "" {
			opts.APTrustAPIUser = partnerConfig.APTrustAPIUser
			opts.APTrustAPIUserFrom = opts.PathToConfigFile
		} else if os.Getenv("APTRUST_API_USER") != "" {
			opts.APTrustAPIUser = os.Getenv("APTRUST_API_USER")
			opts.APTrustAPIUserFrom = "ENV['APTRUST_API_USER']"
		} else if os.Getenv("PHAROS_API_USER") != "" {
			opts.APTrustAPIUser = os.Getenv("PHAROS_API_USER")
			opts.APTrustAPIUserFrom = "ENV['PHAROS_API_USER']"
		}
	}
}

// LoadConfigFile loads the Partner Config file, which contains settings
// to connect to AWS S3. We must be able to load this file if certain
// command-line options are not specified.
func (opts *Options) LoadConfigFile() (*PartnerConfig, error) {
	var err error
	defaultConfigFile, _ := partner.DefaultConfigFile()
	if opts.PathToConfigFile == "" && partner.DefaultConfigFileExists() {
		opts.PathToConfigFile, err = fileutil.RelativeToAbsPath(defaultConfigFile)
		if err != nil {
			opts.addError(fmt.Sprintf("Cannot determine absolute path of %s: %v\n",
				opts.PathToConfigFile, err.Error()))
			return nil, err
		}
	}
	partnerConfig, err := LoadPartnerConfig(opts.PathToConfigFile)
	if err != nil {
		opts.addError(fmt.Sprintf("Cannot load config file from %s: %v\n",
			opts.PathToConfigFile, err.Error()))
		return nil, err
	}
	//for _, warning := range partnerConfig.Warnings() {
	//	fmt.Fprintln(os.Stderr, "WARNING -", warning)
	//}
	return partnerConfig, nil
}

// addError adds an error to Options.Errors
func (opts *Options) addError(message string) {
	if opts.errors == nil {
		opts.errors = make([]string, 0)
	}
	opts.errors = append(opts.errors, message)
}

// Returns true of the options have any errors or missing
// required values.
func (opts *Options) HasErrors() bool {
	return opts.errors != nil && len(opts.errors) > 0
}

// AllErrorsAsString returns all errors as a single string,
// with each error ending in a newline. This is suitable
// for printing to STDOUT/STDERR.
func (opts *Options) AllErrorsAsString() string {
	errors := opts.Errors()
	if len(errors) > 0 {
		return strings.Join(errors, "\n")
	}
	return ""
}

// Errors returns a list of errors, such as invalid or
// missing params.
func (opts *Options) Errors() []string {
	if opts.errors == nil {
		opts.ClearErrors()
	}
	return opts.errors
}

// ClearErrors clears all errors. This is used in testing.
func (opts *Options) ClearErrors() {
	opts.errors = make([]string, 0)
}
<commit_msg>When checking for config files: Or, not And.<commit_after>package common

import (
	"fmt"
	"github.com/APTrust/exchange/util/fileutil"
	"github.com/APTrust/exchange/util/partner"
	"os"
	"strings"
)

type Options struct {
	// PathToConfigFile is the path the APTrust partner config
	// file. If not specified, this defaults to ~/.aptrust_partner.conf.
	// This can be omitted entirely if you supply the -bucket and -key
	// options on the command line. Any required options not specified
	// on the command line will be pulled from this file.
	PathToConfigFile string
	// AccessKeyId is your AWS access key id. Used for authentication.
	AccessKeyId string
	// AccessKeyFrom describes the source from which the Options object
	// loaded the AWS AccessKeyId. This is used only for testing and debugging.
	AccessKeyFrom string
	// APTrustAPIKey is the key to connect to APTrust REST API.
	// The key must belong to APTrustAPIUser.
	APTrustAPIKey string
	// APTrustAPIKeyFrom tells whether the API key came from the config
	// file or the environment.
	APTrustAPIKeyFrom string
	// APTrustAPIKey is the user email address to connect to APTrust REST API.
	APTrustAPIUser string
	// APTrustAPIUserFrom tells whether the API user came from the config
	// file or the environment.
	APTrustAPIUserFrom string
	// SecretAccessKey is the AWS Secret Access Key used to access your
	// S3 bucket.
	SecretAccessKey string
	// SecretKeyFrom describes the source from which the Options object
	// loaded the AWS SecretAccessKey. This is used only for testing and
	// debugging.
	SecretKeyFrom string
	// Region is the AWS S3 region to connect to.
	Region string
	// Bucket is the name of the bucket you're working with.
	Bucket string
	// Key is the name of the S3 key to download, list, or delete.
	Key string
	// Dir is the directory into which the S3 object should be downloaded.
	// This option is for downloads only.
	Dir string
	// ContentType is the content type of the object being uploaded
	// to S3. This option applies to uploads only, and can be left
	// empty.
	ContentType string
	// Metadata is optional metadata to be saved in S3 when uploading
	// a file.
	Metadata map[string]string
	// FileToUpload is the path the file that should be uploaded to S3.
	// This is required for apt_upload only, and is ignored elsewhere.
	FileToUpload string
	// PharosURL is the URL of the Pharos production or demo system.
	PharosURL string
	// OutputFormat specifies how the program should print its results
	// to STDOUT. Options are "text" and "json".
	OutputFormat string
	// Debug indicates whether we should print debug output to Stdout.
	Debug bool
	// error contains a list of errors describing why these options are
	// not valid for an operation like upload or download.
	errors []string
}

// SetAndVerifyDownloadOptions tries to fill in options
// that were not supplied on the command line with those
// specified in the APTrust partner config file. It also
// verifies that all required and allowed values are present.
// Check opts.HasErrors() after calling this, to see if we
// have sufficient options info to proceed with a download.
func (opts *Options) SetAndVerifyDownloadOptions() {
	opts.ClearErrors()
	if opts.OutputFormat == "" {
		opts.OutputFormat = "text"
	}
	opts.MergeConfigFileOptions()
	opts.VerifyOutputFormat()
	opts.EnsureDownloadDirIsSet()
	opts.VerifyRequiredDownloadOptions()
}

// SetAndVerifyUploadOptions
func (opts *Options) SetAndVerifyUploadOptions() {
	opts.ClearErrors()
	if opts.OutputFormat == "" {
		opts.OutputFormat = "text"
	}
	opts.MergeConfigFileOptions()
	opts.VerifyOutputFormat()
	opts.VerifyRequiredUploadOptions()
}

// VerifyRequiredDownloadOptions checks to see that all
// required download options are set.
func (opts *Options) VerifyRequiredDownloadOptions() {
	if opts.Key == "" {
		opts.addError("Param -key must be specified on the command line")
	}
	if opts.Bucket == "" {
		opts.addError("Param -bucket must be specified on the command line or in the config file")
	}
	if opts.AccessKeyId == "" {
		opts.addError("Cannot find AWS_ACCESS_KEY_ID in environment or config file")
	}
	if opts.SecretAccessKey == "" {
		opts.addError("Cannot find AWS_SECRET_ACCESS_KEY in environment or config file")
	}
}

// VerifyRequiredUploadOptions checks to see that all
// required upload options are set.
func (opts *Options) VerifyRequiredUploadOptions() {
	if opts.Bucket == "" {
		opts.addError("Param -bucket must be specified on the command line or in the config file")
	}
	if opts.AccessKeyId == "" {
		opts.addError("Cannot find AWS_ACCESS_KEY_ID in environment or config file")
	}
	if opts.SecretAccessKey == "" {
		opts.addError("Cannot find AWS_SECRET_ACCESS_KEY in environment or config file")
	}
	if opts.FileToUpload == "" {
		opts.addError("You must specify a file to upload")
	}
}

// VerifyOutputFormat makes sure the user specified a valid output format.
func (opts *Options) VerifyOutputFormat() {
	if opts.OutputFormat != "text" && opts.OutputFormat != "json" {
		opts.addError("Param -format must be either 'text' or 'json'")
	}
}

func (opts *Options) VerifyRequiredAPICredentials() {
	if opts.APTrustAPIUser == "" {
		opts.addError("Cannot find APTrust API user in environment or config file")
	}
	if opts.APTrustAPIKey == "" {
		opts.addError("Cannot find APTrust API key in environment or config file")
	}
}

// EnsureDownloadDirIsSet makes sure we have a directory to download the file into.
func (opts *Options) EnsureDownloadDirIsSet() {
	var err error
	// If the dir setting has a tilde, expand it to the user's
	// home directory. This call fails if the system cannot
	// determine the user.
	dir, _ := fileutil.ExpandTilde(opts.Dir)
	if dir == "" {
		dir = opts.Dir
	}
	if dir == "" {
		dir, err = os.Getwd()
		if err != nil {
			dir, err = fileutil.RelativeToAbsPath(".")
			if err != nil {
				dir = "."
			}
		}
	}
	opts.Dir = dir
}

// MergeConfigFileOptions supplements command-line options with
// the default values the user specified in their APTrust
// parner config file.
//
// If the user left some options unspecified on the command line,
// load them from the config file, if we can. If the user specified
// a config file, use that. Otherwise, use the default config file
// in ~/.aptrust_partner.conf or %HOMEPATH%\.aptrust_partner.conf
func (opts *Options) MergeConfigFileOptions() {
	partnerConfig := &PartnerConfig{}
	if opts.PathToConfigFile != "" || partner.DefaultConfigFileExists() {
		var err error
		partnerConfig, err = opts.LoadConfigFile()
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
	}
	if opts.Bucket == "" && partnerConfig.RestorationBucket != "" {
		opts.Bucket = partnerConfig.RestorationBucket
	}
	if opts.Dir == "" && partnerConfig.DownloadDir != "" {
		opts.Dir = partnerConfig.DownloadDir
	}
	if opts.AccessKeyId == "" {
		if partnerConfig.AwsAccessKeyId != "" {
			opts.AccessKeyId = partnerConfig.AwsAccessKeyId
			opts.AccessKeyFrom = opts.PathToConfigFile
		} else {
			opts.AccessKeyId = os.Getenv("AWS_ACCESS_KEY_ID")
			opts.AccessKeyFrom = "ENV['AWS_ACCESS_KEY_ID']"
		}
	}
	if opts.SecretAccessKey == "" {
		if partnerConfig.AwsSecretAccessKey != "" {
			opts.SecretAccessKey = partnerConfig.AwsSecretAccessKey
			opts.AccessKeyFrom = opts.PathToConfigFile
		} else {
			opts.SecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
			opts.SecretKeyFrom = "ENV['AWS_SECRET_ACCESS_KEY']"
		}
	}
	if opts.APTrustAPIKey == "" {
		if partnerConfig.APTrustAPIKey != "" {
			opts.APTrustAPIKey = partnerConfig.APTrustAPIKey
			opts.APTrustAPIKeyFrom = opts.PathToConfigFile
		} else if os.Getenv("APTRUST_API_KEY") != "" {
			opts.APTrustAPIKey = os.Getenv("APTRUST_API_KEY")
			opts.APTrustAPIKeyFrom = "ENV['APTRUST_API_KEY']"
		} else if os.Getenv("PHAROS_API_KEY") != "" {
			opts.APTrustAPIKey = os.Getenv("PHAROS_API_KEY")
			opts.APTrustAPIKeyFrom = "ENV['PHAROS_API_KEY']"
		}
	}
	if opts.APTrustAPIUser == "" {
		if partnerConfig.APTrustAPIUser != "" {
			opts.APTrustAPIUser = partnerConfig.APTrustAPIUser
			opts.APTrustAPIUserFrom = opts.PathToConfigFile
		} else if os.Getenv("APTRUST_API_USER") != "" {
			opts.APTrustAPIUser = os.Getenv("APTRUST_API_USER")
			opts.APTrustAPIUserFrom = "ENV['APTRUST_API_USER']"
		} else if os.Getenv("PHAROS_API_USER") != "" {
			opts.APTrustAPIUser = os.Getenv("PHAROS_API_USER")
			opts.APTrustAPIUserFrom = "ENV['PHAROS_API_USER']"
		}
	}
}

// LoadConfigFile loads the Partner Config file, which contains settings
// to connect to AWS S3. We must be able to load this file if certain
// command-line options are not specified.
func (opts *Options) LoadConfigFile() (*PartnerConfig, error) {
	var err error
	defaultConfigFile, _ := partner.DefaultConfigFile()
	if opts.PathToConfigFile == "" && partner.DefaultConfigFileExists() {
		opts.PathToConfigFile, err = fileutil.RelativeToAbsPath(defaultConfigFile)
		if err != nil {
			opts.addError(fmt.Sprintf("Cannot determine absolute path of %s: %v\n",
				opts.PathToConfigFile, err.Error()))
			return nil, err
		}
	}
	partnerConfig, err := LoadPartnerConfig(opts.PathToConfigFile)
	if err != nil {
		opts.addError(fmt.Sprintf("Cannot load config file from %s: %v\n",
			opts.PathToConfigFile, err.Error()))
		return nil, err
	}
	//for _, warning := range partnerConfig.Warnings() {
	//	fmt.Fprintln(os.Stderr, "WARNING -", warning)
	//}
	return partnerConfig, nil
}

// addError adds an error to Options.Errors
func (opts *Options) addError(message string) {
	if opts.errors == nil {
		opts.errors = make([]string, 0)
	}
	opts.errors = append(opts.errors, message)
}

// Returns true of the options have any errors or missing
// required values.
func (opts *Options) HasErrors() bool {
	return opts.errors != nil && len(opts.errors) > 0
}

// AllErrorsAsString returns all errors as a single string,
// with each error ending in a newline. This is suitable
// for printing to STDOUT/STDERR.
func (opts *Options) AllErrorsAsString() string {
	errors := opts.Errors()
	if len(errors) > 0 {
		return strings.Join(errors, "\n")
	}
	return ""
}

// Errors returns a list of errors, such as invalid or
// missing params.
func (opts *Options) Errors() []string {
	if opts.errors == nil {
		opts.ClearErrors()
	}
	return opts.errors
}

// ClearErrors clears all errors. This is used in testing.
func (opts *Options) ClearErrors() {
	opts.errors = make([]string, 0)
}
<|endoftext|>


<commit_before>package auth

import (
	"net/http"

	"github.com/labstack/echo"
)

var (
	config *Config
)

// New creates a new instance of the users Controller
func New(e *echo.Group, c *Config) {
	config = c
	group := e.Group("/auth")
	group.Post("/login", login)
}

func login(c echo.Context) error {
	username := c.FormValue("username")
	password := c.FormValue("password")

	if username == "opi" && password == "opi" {
		t := GenerateToken("hahaha", []string{"admin"})
		return c.JSON(http.StatusOK, map[string]string{
			"token": t,
		})
	}

	return echo.ErrUnauthorized
}
<commit_msg>refactor(login): post with json object to /login<commit_after>package auth

import (
	"net/http"

	"github.com/labstack/echo"
)

type LoginModel struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

var (
	config *Config
)

// New creates a new instance of the users Controller
func New(e *echo.Group, c *Config) {
	config = c
	group := e.Group("/auth")
	group.Post("/login", login)
}

func login(c echo.Context) error {
	login := new(LoginModel)
	if err := c.Bind(&login); err != nil {
		return err
	}

	if login.Username == "opi" && login.Password == "opi" {
		t := GenerateToken("hahaha", []string{"admin"})
		return c.JSON(http.StatusOK, map[string]string{
			"token": t,
		})
	}

	return echo.ErrUnauthorized
}
<|endoftext|>


<commit_before>package htlcswitch

import (
	"bytes"
	"fmt"

	sphinx "github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/htlcswitch/hop"
	"github.com/lightningnetwork/lnd/lnwire"
)

// ClearTextError is an interface which is implemented by errors that occur
// when we know the underlying wire failure message. These errors are the
// opposite to opaque errors which are onion-encrypted blobs only understandable
// to the initiating node. ClearTextErrors are used when we fail a htlc at our
// node, or one of our initiated payments failed and we can decrypt the onion
// encrypted error fully.
type ClearTextError interface {
	error

	// WireMessage extracts a valid wire failure message from an internal
	// error which may contain additional metadata (which should not be
	// exposed to the network). This value may be nil in the case where
	// an unknown wire error is returned by one of our peers.
	WireMessage() lnwire.FailureMessage
}

// ForwardingError wraps an lnwire.FailureMessage in a struct that also
// includes the source of the error.
type ForwardingError struct {
	// FailureSourceIdx is the index of the node that sent the failure. With
	// this information, the dispatcher of a payment can modify their set of
	// candidate routes in response to the type of failure extracted. Index
	// zero is the self node.
	FailureSourceIdx int

	// ExtraMsg is an additional error message that callers can provide in
	// order to provide context specific error details.
	ExtraMsg string

	// msg is the wire message associated with the error. This value may
	// be nil in the case where we fail to decode failure message sent by
	// a peer.
	msg lnwire.FailureMessage
}

// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
//
// Note this is part of the ClearTextError interface.
func (f *ForwardingError) WireMessage() lnwire.FailureMessage {
	return f.msg
}

// Error implements the built-in error interface. We use this method to allow
// the switch or any callers to insert additional context to the error message
// returned.
func (f *ForwardingError) Error() string {
	if f.ExtraMsg == "" {
		return fmt.Sprintf("%v@%v", f.msg, f.FailureSourceIdx)
	}

	return fmt.Sprintf(
		"%v@%v: %v", f.msg, f.FailureSourceIdx, f.ExtraMsg,
	)
}

// NewForwardingError creates a new payment error which wraps a wire error
// with additional metadata.
func NewForwardingError(failure lnwire.FailureMessage, index int,
	extraMsg string) *ForwardingError {

	return &ForwardingError{
		FailureSourceIdx: index,
		msg:              failure,
		ExtraMsg:         extraMsg,
	}
}

// NewUnknownForwardingError returns a forwarding error which has a nil failure
// message. This constructor should only be used in the case where we cannot
// decode the failure we have received from a peer.
func NewUnknownForwardingError(index int) *ForwardingError {
	return &ForwardingError{
		FailureSourceIdx: index,
	}
}

// ErrorDecrypter is an interface that is used to decrypt the onion encrypted
// failure reason an extra out a well formed error.
type ErrorDecrypter interface {
	// DecryptError peels off each layer of onion encryption from the first
	// hop, to the source of the error. A fully populated
	// lnwire.FailureMessage is returned along with the source of the
	// error.
	DecryptError(lnwire.OpaqueReason) (*ForwardingError, error)
}

// UnknownEncrypterType is an error message used to signal that an unexpected
// EncrypterType was encountered during decoding.
type UnknownEncrypterType hop.EncrypterType

// Error returns a formatted error indicating the invalid EncrypterType.
func (e UnknownEncrypterType) Error() string {
	return fmt.Sprintf("unknown error encrypter type: %d", e)
}

// OnionErrorDecrypter is the interface that provides onion level error
// decryption.
type OnionErrorDecrypter interface {
	// DecryptError attempts to decrypt the passed encrypted error response.
	// The onion failure is encrypted in backward manner, starting from the
	// node where error have occurred. As a result, in order to decrypt the
	// error we need get all shared secret and apply decryption in the
	// reverse order.
	DecryptError(encryptedData []byte) (*sphinx.DecryptedError, error)
}

// SphinxErrorDecrypter wraps the sphinx data SphinxErrorDecrypter and maps the
// returned errors to concrete lnwire.FailureMessage instances.
type SphinxErrorDecrypter struct {
	OnionErrorDecrypter
}

// DecryptError peels off each layer of onion encryption from the first hop, to
// the source of the error. A fully populated lnwire.FailureMessage is returned
// along with the source of the error.
//
// NOTE: Part of the ErrorDecrypter interface.
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
	*ForwardingError, error) {

	failure, err := s.OnionErrorDecrypter.DecryptError(reason)
	if err != nil {
		return nil, err
	}

	// Decode the failure. If an error occurs, we leave the failure message
	// field nil.
	r := bytes.NewReader(failure.Message)
	failureMsg, err := lnwire.DecodeFailure(r, 0)
	if err != nil {
		return NewUnknownForwardingError(failure.SenderIdx), nil
	}

	return NewForwardingError(failureMsg, failure.SenderIdx, ""), nil
}

// A compile time check to ensure ErrorDecrypter implements the Deobfuscator
// interface.
var _ ErrorDecrypter = (*SphinxErrorDecrypter)(nil)
<commit_msg>htlcswitch: add LinkError implementation of ClearTextError<commit_after>package htlcswitch

import (
	"bytes"
	"fmt"

	sphinx "github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/htlcswitch/hop"
	"github.com/lightningnetwork/lnd/lnwire"
)

// ClearTextError is an interface which is implemented by errors that occur
// when we know the underlying wire failure message. These errors are the
// opposite to opaque errors which are onion-encrypted blobs only understandable
// to the initiating node. ClearTextErrors are used when we fail a htlc at our
// node, or one of our initiated payments failed and we can decrypt the onion
// encrypted error fully.
type ClearTextError interface {
	error

	// WireMessage extracts a valid wire failure message from an internal
	// error which may contain additional metadata (which should not be
	// exposed to the network). This value may be nil in the case where
	// an unknown wire error is returned by one of our peers.
	WireMessage() lnwire.FailureMessage
}

// LinkError is an implementation of the ClearTextError interface which
// represents failures that occur on our incoming or outgoing link.
type LinkError struct {
	// msg returns the wire failure associated with the error.
	// This value should *not* be nil, because we should always
	// know the failure type for failures which occur at our own
	// node.
	msg lnwire.FailureMessage
}

// NewLinkError returns a LinkError with the failure message provided.
// The failure message provided should *not* be nil, because we should
// always know the failure type for failures which occur at our own node.
func NewLinkError(msg lnwire.FailureMessage) *LinkError {
	return &LinkError{msg: msg}
}

// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value should never be nil for LinkErrors,
// because we are the ones failing the htlc.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) WireMessage() lnwire.FailureMessage {
	return l.msg
}

// Error returns the string representation of a link error.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) Error() string {
	return l.msg.Error()
}

// ForwardingError wraps an lnwire.FailureMessage in a struct that also
// includes the source of the error.
type ForwardingError struct {
	// FailureSourceIdx is the index of the node that sent the failure. With
	// this information, the dispatcher of a payment can modify their set of
	// candidate routes in response to the type of failure extracted. Index
	// zero is the self node.
	FailureSourceIdx int

	// ExtraMsg is an additional error message that callers can provide in
	// order to provide context specific error details.
	ExtraMsg string

	// msg is the wire message associated with the error. This value may
	// be nil in the case where we fail to decode failure message sent by
	// a peer.
	msg lnwire.FailureMessage
}

// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
//
// Note this is part of the ClearTextError interface.
func (f *ForwardingError) WireMessage() lnwire.FailureMessage {
	return f.msg
}

// Error implements the built-in error interface. We use this method to allow
// the switch or any callers to insert additional context to the error message
// returned.
func (f *ForwardingError) Error() string {
	if f.ExtraMsg == "" {
		return fmt.Sprintf("%v@%v", f.msg, f.FailureSourceIdx)
	}

	return fmt.Sprintf(
		"%v@%v: %v", f.msg, f.FailureSourceIdx, f.ExtraMsg,
	)
}

// NewForwardingError creates a new payment error which wraps a wire error
// with additional metadata.
func NewForwardingError(failure lnwire.FailureMessage, index int,
	extraMsg string) *ForwardingError {

	return &ForwardingError{
		FailureSourceIdx: index,
		msg:              failure,
		ExtraMsg:         extraMsg,
	}
}

// NewUnknownForwardingError returns a forwarding error which has a nil failure
// message. This constructor should only be used in the case where we cannot
// decode the failure we have received from a peer.
func NewUnknownForwardingError(index int) *ForwardingError {
	return &ForwardingError{
		FailureSourceIdx: index,
	}
}

// ErrorDecrypter is an interface that is used to decrypt the onion encrypted
// failure reason an extra out a well formed error.
type ErrorDecrypter interface {
	// DecryptError peels off each layer of onion encryption from the first
	// hop, to the source of the error. A fully populated
	// lnwire.FailureMessage is returned along with the source of the
	// error.
	DecryptError(lnwire.OpaqueReason) (*ForwardingError, error)
}

// UnknownEncrypterType is an error message used to signal that an unexpected
// EncrypterType was encountered during decoding.
type UnknownEncrypterType hop.EncrypterType

// Error returns a formatted error indicating the invalid EncrypterType.
func (e UnknownEncrypterType) Error() string {
	return fmt.Sprintf("unknown error encrypter type: %d", e)
}

// OnionErrorDecrypter is the interface that provides onion level error
// decryption.
type OnionErrorDecrypter interface {
	// DecryptError attempts to decrypt the passed encrypted error response.
	// The onion failure is encrypted in backward manner, starting from the
	// node where error have occurred. As a result, in order to decrypt the
	// error we need get all shared secret and apply decryption in the
	// reverse order.
	DecryptError(encryptedData []byte) (*sphinx.DecryptedError, error)
}

// SphinxErrorDecrypter wraps the sphinx data SphinxErrorDecrypter and maps the
// returned errors to concrete lnwire.FailureMessage instances.
type SphinxErrorDecrypter struct {
	OnionErrorDecrypter
}

// DecryptError peels off each layer of onion encryption from the first hop, to
// the source of the error. A fully populated lnwire.FailureMessage is returned
// along with the source of the error.
//
// NOTE: Part of the ErrorDecrypter interface.
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
	*ForwardingError, error) {

	failure, err := s.OnionErrorDecrypter.DecryptError(reason)
	if err != nil {
		return nil, err
	}

	// Decode the failure. If an error occurs, we leave the failure message
	// field nil.
	r := bytes.NewReader(failure.Message)
	failureMsg, err := lnwire.DecodeFailure(r, 0)
	if err != nil {
		return NewUnknownForwardingError(failure.SenderIdx), nil
	}

	return NewForwardingError(failureMsg, failure.SenderIdx, ""), nil
}

// A compile time check to ensure ErrorDecrypter implements the Deobfuscator
// interface.
var _ ErrorDecrypter = (*SphinxErrorDecrypter)(nil)
<|endoftext|>