<commit_before>// package ui provides the UI components of Goed.
package ui

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/tcolar/goed/actions"
	"github.com/tcolar/goed/backend"
	"github.com/tcolar/goed/core"
	"github.com/tcolar/termbox-go"
)

// Editor is goed's main Editor pane (singleton)
type Editor struct {
	Cmdbar     *Cmdbar
	config     *core.Config
	Statusbar  *Statusbar
	Fg, Bg     core.Style
	theme      *core.Theme
	Cols       []*Col
	curViewId  int64
	CurCol     *Col
	cmdOn      bool
	pctw, pcth float64
	evtState   *EvtState
	term       core.Term
	views      map[int64]*View
}

func NewEditor() *Editor {
	return &Editor{
		term:   core.NewTermBox(),
		config: core.LoadConfig(core.ConfFile),
		views:  map[int64]*View{},
	}
}

// Editor with Mock terminal for testing
func NewMockEditor() *Editor {
	return &Editor{
		term:   core.NewMockTerm(),
		config: core.LoadConfig("config.toml"),
		views:  map[int64]*View{},
	}
}

func (e *Editor) Dispatch(action core.Action) {
	core.Bus.Dispatch(action)
}

// Start starts-up the editor
func (e *Editor) Start(locs []string) {
	err := e.term.Init()
	if err != nil {
		panic(err)
	}

	defer func() {
		// TODO: should set it to original value, but how to read it ??
		e.term.SetMouseMode(termbox.MouseClick)
		e.term.Close()
	}()
	e.term.SetExtendedColors(core.Colors == 256)
	e.evtState = &EvtState{}
	e.theme, err = core.ReadTheme(core.FindResource(path.Join("themes", e.config.Theme)))
	if err != nil {
		panic(err)
	}
	e.Fg = e.theme.Fg
	e.Bg = e.theme.Bg

	h, w := e.term.Size()
	e.Cmdbar = &Cmdbar{}
	e.Cmdbar.SetBounds(0, 0, 0, w)
	e.Statusbar = &Statusbar{}
	e.Statusbar.SetBounds(h-1, 0, h-1, w)
	dirs := []string{}
	files := []string{}
	for _, loc := range locs {
		if stat, err := os.Stat(loc); err == nil && stat.IsDir() {
			dirs = append(dirs, loc)
		} else {
			files = append(files, loc)
		}
	}
	if len(dirs) == 0 {
		if len(files) > 0 {
			dirs = []string{path.Dir(locs[0])}
		} else {
			dirs = []string{"."}
		}
	}
	e.Cols = append(e.Cols, &Col{WidthRatio: 1.0})
	ratio := 1.0 / float64(len(dirs))
	for _, dir := range dirs {
		view := e.NewView(dir)
		view.HeightRatio = ratio
		e.Cols[0].Views = append(e.Cols[0].Views, view.Id())
		e.Open(dir, view.Id(), "", true)
	}
	e.CurCol = e.Cols[0]
	e.curViewId = e.CurCol.Views[0]
	if len(files) > 0 {
		e.CurCol.WidthRatio = 0.2
		c := &Col{WidthRatio: 0.8}
		ratio := 1.0 / float64(len(files))
		for _, f := range files {
			view := e.NewView(f)
			view.HeightRatio = ratio
			c.Views = append(c.Views, view.Id())
			e.Open(f, view.Id(), "", true)
		}
		e.Cols = append(e.Cols, c)
		e.CurCol = c
		e.curViewId = c.Views[0]
	}

	actions.EdResize(e.term.Size())

	actions.EdRender()

	go e.autoScroller()

	if !core.Testing {
		e.EventLoop()
	}
}

// Open opens a given location in the editor (in the given view)
// or new view if viewId < 0
func (e Editor) Open(loc string, viewId int64, rel string, create bool) (int64, error) {
	loc = strings.TrimSpace(loc)
	rel = strings.TrimSpace(rel)
	if len(rel) > 0 && !strings.HasPrefix(loc, string(os.PathSeparator)) {
		loc = path.Join(rel, loc)
	}
	// make it absolute
	loc, err := filepath.Abs(loc)
	if err != nil {
		return -1, err
	}
	stat, err := os.Stat(loc)
	newFile := false
	if os.IsNotExist(err) {
		if !create {
			return -1, err
		}
		newFile = true
	}
	title := filepath.Base(loc)
	if !newFile && stat.IsDir() {
		loc += string(os.PathSeparator)
		title += string(os.PathSeparator)
	}
	nv := false
	var view core.Viewable
	if viewId < 0 {
		view = e.NewFileView(loc)
		nv = true
	} else {
		view = e.ViewById(viewId)
	}
	view.Reset()
	view.SetTitle(title)
	if newFile || !stat.IsDir() {
		err = e.openFile(loc, view)
	} else {
		err = e.openDir(loc, view)
	}
	if err != nil {
		return -1, err
	}
	if nv {
		if stat == nil || stat.IsDir() {
			c := e.ColNarrowest()
			ci := len(c.Views) - 1
			v := e.views[c.Views[ci]]
			e.InsertView(view.(*View), v, 0.5)
		} else {
			e.InsertView(view.(*View), e.CurView().(*View), 0.5)
		}
	}
	view.SetWorkDir(filepath.Dir(loc))
	return view.Id(), nil
}

// OpenDir opens a directory listing
func (e *Editor) openDir(loc string, view core.Viewable) error {
	view.(*View).highlighter = &TermHighlighter{}
	args := append([]string{"ls"}, core.OsLsArgs...)
	title := filepath.Base(loc) + "/"
	backend, err := backend.NewMemBackendCmd(args, loc, view.Id(), &title, true)
	if err != nil {
		return err
	}
	view.SetBackend(backend)
	e.SetStatus(fmt.Sprintf("%v", view.WorkDir()))
	return nil
}

// OpenFile opens a file in the editor
func (e *Editor) openFile(loc string, view core.Viewable) error {
	if !core.IsTextFile(loc) {
		return fmt.Errorf("Binary file ? %s", loc)
	}
	b, err := backend.NewFileBackend(loc, view.Id())
	if err != nil {
		return err
	}
	view.SetBackend(b)
	e.SetStatus(fmt.Sprintf("%v  [%d]", view.WorkDir(), view.Id()))
	view.SetDirty(false)
	return nil
}

func (e Editor) SetStatusErr(s string) {
	if e.Statusbar == nil {
		return
	}
	e.Statusbar.msg = s
	e.Statusbar.isErr = true
	e.Statusbar.Render()
}
func (e Editor) SetStatus(s string) {
	if e.Statusbar == nil {
		return
	}
	e.Statusbar.msg = s
	e.Statusbar.msg = s
	e.Statusbar.isErr = false
	e.Statusbar.Render()
}

func (e Editor) Config() core.Config {
	return *e.config
}

func (e Editor) Theme() *core.Theme {
	return e.theme
}

func (e Editor) CurView() core.Viewable {
	v, found := e.views[e.curViewId]
	if !found {
		return nil
	}
	return v
}

func (e Editor) CurViewId() int64 {
	return e.curViewId
}

func (e Editor) SetCursor(y, x int) {
	e.term.SetCursor(x, y)
}

func (e Editor) CmdOn() bool {
	return e.cmdOn
}

func (e *Editor) SetCmdOn(v bool) {
	e.cmdOn = v
}

func (e *Editor) TermFlush() {
	e.term.Flush()
}

func (e *Editor) QuitCheck() bool {
	for _, c := range e.Cols {
		for _, vi := range c.Views {
			v, found := e.views[vi]
			if found && !v.canClose() {
				return false
			}
		}
	}
	return true
}

// Handle selection auto scrolling of views
func (e *Editor) autoScroller() {
	action := autoScrollAction{}
	for {
		time.Sleep(200 * time.Millisecond)
		core.Bus.Dispatch(action)
	}
}

type autoScrollAction struct {
}

func (e autoScrollAction) Run() error {
	v := core.Ed.ViewById(core.Ed.CurViewId()).(*View)
	if v == nil {
		return nil
	}
	x, y := v.autoScrollX, v.autoScrollY
	if x == 0 && y == 0 {
		return nil
	}
	if len(v.selections) == 0 {
		return nil
	}
	s := v.selections[0]
	ln := v.CurLine()
	v.offx += x
	v.offy += y
	if y > 0 {
		s.LineTo += y
	} else {
		s.LineFrom += y
	}
	if x > 0 {
		s.ColTo += x
	} else {
		s.ColFrom += x
	}
	// handle scroll / selection "overflows"
	lnLen := v.LineLen(v.Slice(), ln)
	if v.offy >= v.LineCount()-v.LastViewLine() {
		v.offy = v.LineCount() - v.LastViewLine()
	}
	if v.offy < 0 {
		v.offy = 0
	}
	if v.offx > lnLen-v.LastViewCol() {
		v.offx = lnLen - v.LastViewCol()
	}
	if v.offx < 0 {
		v.offx = 0
	}
	if s.LineFrom < 0 {
		s.LineFrom = 0
	} else if s.LineFrom > v.LineCount() {
		s.LineFrom = v.LineCount()
	}
	if s.LineTo < 0 {
		s.LineTo = 0
	} else if s.LineTo > v.LineCount() {
		s.LineTo = v.LineCount()
	}
	if s.ColFrom < 0 {
		s.ColFrom = 0
	} else if s.ColFrom > lnLen {
		s.ColFrom = lnLen
	}
	if s.ColTo < 0 {
		s.ColTo = 0
	} else if s.ColTo > lnLen {
		s.ColTo = lnLen
	}
	s.Normalize()
	v.selections = []core.Selection{
		s,
	}
	core.Ed.Render()
	return nil
}
<commit_msg>Improved view mngmt (#84)<commit_after>// package ui provides the UI components of Goed.
package ui

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/tcolar/goed/actions"
	"github.com/tcolar/goed/backend"
	"github.com/tcolar/goed/core"
	"github.com/tcolar/termbox-go"
)

// Editor is goed's main Editor pane (singleton)
type Editor struct {
	Cmdbar     *Cmdbar
	config     *core.Config
	Statusbar  *Statusbar
	Fg, Bg     core.Style
	theme      *core.Theme
	Cols       []*Col
	curViewId  int64
	CurCol     *Col
	cmdOn      bool
	pctw, pcth float64
	evtState   *EvtState
	term       core.Term
	views      map[int64]*View
}

func NewEditor() *Editor {
	return &Editor{
		term:   core.NewTermBox(),
		config: core.LoadConfig(core.ConfFile),
		views:  map[int64]*View{},
	}
}

// Editor with Mock terminal for testing
func NewMockEditor() *Editor {
	return &Editor{
		term:   core.NewMockTerm(),
		config: core.LoadConfig("config.toml"),
		views:  map[int64]*View{},
	}
}

func (e *Editor) Dispatch(action core.Action) {
	core.Bus.Dispatch(action)
}

// Start starts-up the editor
func (e *Editor) Start(locs []string) {
	err := e.term.Init()
	if err != nil {
		panic(err)
	}

	defer func() {
		// TODO: should set it to original value, but how to read it ??
		e.term.SetMouseMode(termbox.MouseClick)
		e.term.Close()
	}()
	e.term.SetExtendedColors(core.Colors == 256)
	e.evtState = &EvtState{}
	e.theme, err = core.ReadTheme(core.FindResource(path.Join("themes", e.config.Theme)))
	if err != nil {
		panic(err)
	}
	e.Fg = e.theme.Fg
	e.Bg = e.theme.Bg

	h, w := e.term.Size()
	e.Cmdbar = &Cmdbar{}
	e.Cmdbar.SetBounds(0, 0, 0, w)
	e.Statusbar = &Statusbar{}
	e.Statusbar.SetBounds(h-1, 0, h-1, w)
	dirs := []string{}
	files := []string{}
	for _, loc := range locs {
		if stat, err := os.Stat(loc); err == nil && stat.IsDir() {
			dirs = append(dirs, loc)
		} else {
			files = append(files, loc)
		}
	}
	if len(dirs) == 0 {
		if len(files) > 0 {
			dirs = []string{path.Dir(locs[0])}
		} else {
			dirs = []string{"."}
		}
	}
	e.Cols = append(e.Cols, &Col{WidthRatio: 1.0})
	ratio := 1.0 / float64(len(dirs))
	for _, dir := range dirs {
		view := e.NewView(dir)
		view.HeightRatio = ratio
		e.Cols[0].Views = append(e.Cols[0].Views, view.Id())
		e.Open(dir, view.Id(), "", true)
	}
	e.CurCol = e.Cols[0]
	e.curViewId = e.CurCol.Views[0]
	if len(files) > 0 {
		e.CurCol.WidthRatio = 0.2
		c := &Col{WidthRatio: 0.8}
		ratio := 1.0 / float64(len(files))
		for _, f := range files {
			view := e.NewView(f)
			view.HeightRatio = ratio
			c.Views = append(c.Views, view.Id())
			e.Open(f, view.Id(), "", true)
		}
		e.Cols = append(e.Cols, c)
		e.CurCol = c
		e.curViewId = c.Views[0]
	}

	actions.EdResize(e.term.Size())

	actions.EdRender()

	go e.autoScroller()

	if !core.Testing {
		e.EventLoop()
	}
}

// Open opens a given location in the editor (in the given view)
// or new view if viewId < 0
func (e Editor) Open(loc string, viewId int64, rel string, create bool) (int64, error) {
	loc = strings.TrimSpace(loc)
	rel = strings.TrimSpace(rel)
	if len(rel) > 0 && !strings.HasPrefix(loc, string(os.PathSeparator)) {
		loc = path.Join(rel, loc)
	}
	// make it absolute
	loc, err := filepath.Abs(loc)
	if err != nil {
		return -1, err
	}
	stat, err := os.Stat(loc)
	newFile := false
	if os.IsNotExist(err) {
		if !create {
			return -1, err
		}
		newFile = true
	}
	title := filepath.Base(loc)
	if !newFile && stat.IsDir() {
		loc += string(os.PathSeparator)
		title += string(os.PathSeparator)
	}
	nv := false
	var view core.Viewable
	if viewId < 0 {
		view = e.NewFileView(loc)
		nv = true
	} else {
		view = e.ViewById(viewId)
	}
	view.Reset()
	view.SetTitle(title)
	if newFile || !stat.IsDir() {
		err = e.openFile(loc, view)
	} else {
		err = e.openDir(loc, view)
	}
	if err != nil {
		return -1, err
	}
	if nv {
		if stat == nil || stat.IsDir() {
			e.AddDirViewSmart(view.(*View))
		} else {
			e.InsertViewSmart(view.(*View))
		}
	}
	view.SetWorkDir(filepath.Dir(loc))
	return view.Id(), nil
}

// OpenDir opens a directory listing
func (e *Editor) openDir(loc string, view core.Viewable) error {
	view.(*View).highlighter = &TermHighlighter{}
	args := append([]string{"ls"}, core.OsLsArgs...)
	title := filepath.Base(loc) + "/"
	backend, err := backend.NewMemBackendCmd(args, loc, view.Id(), &title, true)
	if err != nil {
		return err
	}
	view.SetBackend(backend)
	e.SetStatus(fmt.Sprintf("%v", view.WorkDir()))
	return nil
}

// OpenFile opens a file in the editor
func (e *Editor) openFile(loc string, view core.Viewable) error {
	if !core.IsTextFile(loc) {
		return fmt.Errorf("Binary file ? %s", loc)
	}
	b, err := backend.NewFileBackend(loc, view.Id())
	if err != nil {
		return err
	}
	view.SetBackend(b)
	e.SetStatus(fmt.Sprintf("%v  [%d]", view.WorkDir(), view.Id()))
	view.SetDirty(false)
	return nil
}

func (e Editor) SetStatusErr(s string) {
	if e.Statusbar == nil {
		return
	}
	e.Statusbar.msg = s
	e.Statusbar.isErr = true
	e.Statusbar.Render()
}
func (e Editor) SetStatus(s string) {
	if e.Statusbar == nil {
		return
	}
	e.Statusbar.msg = s
	e.Statusbar.msg = s
	e.Statusbar.isErr = false
	e.Statusbar.Render()
}

func (e Editor) Config() core.Config {
	return *e.config
}

func (e Editor) Theme() *core.Theme {
	return e.theme
}

func (e Editor) CurView() core.Viewable {
	v, found := e.views[e.curViewId]
	if !found {
		return nil
	}
	return v
}

func (e Editor) CurViewId() int64 {
	return e.curViewId
}

func (e Editor) SetCursor(y, x int) {
	e.term.SetCursor(x, y)
}

func (e Editor) CmdOn() bool {
	return e.cmdOn
}

func (e *Editor) SetCmdOn(v bool) {
	e.cmdOn = v
}

func (e *Editor) TermFlush() {
	e.term.Flush()
}

func (e *Editor) QuitCheck() bool {
	for _, c := range e.Cols {
		for _, vi := range c.Views {
			v, found := e.views[vi]
			if found && !v.canClose() {
				return false
			}
		}
	}
	return true
}

// Handle selection auto scrolling of views
func (e *Editor) autoScroller() {
	action := autoScrollAction{}
	for {
		time.Sleep(200 * time.Millisecond)
		core.Bus.Dispatch(action)
	}
}

type autoScrollAction struct {
}

func (e autoScrollAction) Run() error {
	v := core.Ed.ViewById(core.Ed.CurViewId()).(*View)
	if v == nil {
		return nil
	}
	x, y := v.autoScrollX, v.autoScrollY
	if x == 0 && y == 0 {
		return nil
	}
	if len(v.selections) == 0 {
		return nil
	}
	s := v.selections[0]
	ln := v.CurLine()
	v.offx += x
	v.offy += y
	if y > 0 {
		s.LineTo += y
	} else {
		s.LineFrom += y
	}
	if x > 0 {
		s.ColTo += x
	} else {
		s.ColFrom += x
	}
	// handle scroll / selection "overflows"
	lnLen := v.LineLen(v.Slice(), ln)
	if v.offy >= v.LineCount()-v.LastViewLine() {
		v.offy = v.LineCount() - v.LastViewLine()
	}
	if v.offy < 0 {
		v.offy = 0
	}
	if v.offx > lnLen-v.LastViewCol() {
		v.offx = lnLen - v.LastViewCol()
	}
	if v.offx < 0 {
		v.offx = 0
	}
	if s.LineFrom < 0 {
		s.LineFrom = 0
	} else if s.LineFrom > v.LineCount() {
		s.LineFrom = v.LineCount()
	}
	if s.LineTo < 0 {
		s.LineTo = 0
	} else if s.LineTo > v.LineCount() {
		s.LineTo = v.LineCount()
	}
	if s.ColFrom < 0 {
		s.ColFrom = 0
	} else if s.ColFrom > lnLen {
		s.ColFrom = lnLen
	}
	if s.ColTo < 0 {
		s.ColTo = 0
	} else if s.ColTo > lnLen {
		s.ColTo = lnLen
	}
	s.Normalize()
	v.selections = []core.Selection{
		s,
	}
	core.Ed.Render()
	return nil
}
<|endoftext|>


<commit_before>package stripe

import (
	"socialapi/models/paymentmodel"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/stripe/stripe-go"
	stripeSub "github.com/stripe/stripe-go/sub"
)

func TestCreateAndFindSubscription(t *testing.T) {
	Convey("Given plan and customer id", t, func() {
		plan, err := FindPlanByTitleAndInterval(StartingPlan, StartingInterval)

		So(err, ShouldBeNil)
		So(plan, ShouldNotBeNil)
		So(plan.ProviderPlanId, ShouldNotEqual, "")

		token, accId, email := generateFakeUserInfo()
		customer, err := CreateCustomer(token, accId, email)

		So(err, ShouldBeNil)
		So(customer, ShouldNotBeNil)

		sub, err := CreateSubscription(customer, plan)
		So(err, ShouldBeNil)

		subParams := &stripe.SubParams{
			Customer: customer.ProviderCustomerId,
			Plan:     plan.ProviderPlanId,
		}

		stripeSubscriptionId := sub.ProviderSubscriptionId

		s, err := stripeSub.Get(stripeSubscriptionId, subParams)

		Convey("Then it should create subscription in Stripe", func() {
			So(err, ShouldBeNil)
			So(s.Id, ShouldEqual, stripeSubscriptionId)
		})

		Convey("Then it should save subscription", func() {
			subs, err := FindCustomerActiveSubscriptions(customer)

			So(err, ShouldBeNil)
			So(len(subs), ShouldEqual, 1)

			sub := subs[0]
			So(sub.ProviderSubscriptionId, ShouldEqual, stripeSubscriptionId)
		})
	})
}

func TestCancelSubscription(t *testing.T) {
	Convey("Given customer already subscribed to a plan", t,
		createCustomerFn(func(accId string, c *paymentmodel.Customer) {
			plan, err := FindPlanByTitleAndInterval(StartingPlan, StartingInterval)
			So(err, ShouldBeNil)
			So(plan, ShouldNotBeNil)

			sub, err := CreateSubscription(c, plan)
			So(err, ShouldBeNil)

			err = CancelSubscription(c, sub)
			So(err, ShouldBeNil)

			Convey("When customer cancels subscription", func() {
				Convey("Then it should delete subscription in Stripe", func() {
					subParams := &stripe.SubParams{
						Customer: c.ProviderCustomerId,
					}

					externalSub, err := stripeSub.Get(sub.ProviderSubscriptionId, subParams)
					So(err, ShouldBeNil)

					So(externalSub.Status, ShouldEqual, true)
				})

				Convey("Then it should update subsciption", func() {
					subscriptions, err := FindCustomerSubscriptions(c)
					So(err, ShouldBeNil)

					So(len(subscriptions), ShouldEqual, 1)

					current_subscription := subscriptions[0]
					So(current_subscription.State, ShouldEqual, SubscriptionStateCanceled)
				})
			})
		}),
	)
}
<commit_msg>payment: fix broken subscription tests<commit_after>package stripe

import (
	"socialapi/models/paymentmodel"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/stripe/stripe-go"
	stripeSub "github.com/stripe/stripe-go/sub"
)

func TestCreateAndFindSubscription(t *testing.T) {
	Convey("Given plan and customer id", t, func() {
		plan, err := FindPlanByTitleAndInterval(StartingPlan, StartingInterval)

		So(err, ShouldBeNil)
		So(plan, ShouldNotBeNil)
		So(plan.ProviderPlanId, ShouldNotEqual, "")

		token, accId, email := generateFakeUserInfo()
		customer, err := CreateCustomer(token, accId, email)

		So(err, ShouldBeNil)
		So(customer, ShouldNotBeNil)

		sub, err := CreateSubscription(customer, plan)
		So(err, ShouldBeNil)

		subParams := &stripe.SubParams{
			Customer: customer.ProviderCustomerId,
			Plan:     plan.ProviderPlanId,
		}

		stripeSubscriptionId := sub.ProviderSubscriptionId

		s, err := stripeSub.Get(stripeSubscriptionId, subParams)

		Convey("Then it should create subscription in Stripe", func() {
			So(err, ShouldBeNil)
			So(s.Id, ShouldEqual, stripeSubscriptionId)
		})

		Convey("Then it should save subscription", func() {
			subs, err := FindCustomerActiveSubscriptions(customer)

			So(err, ShouldBeNil)
			So(len(subs), ShouldEqual, 1)

			sub := subs[0]
			So(sub.ProviderSubscriptionId, ShouldEqual, stripeSubscriptionId)
		})
	})
}

func TestCancelSubscription(t *testing.T) {
	Convey("Given customer already subscribed to a plan", t,
		createCustomerFn(func(accId string, c *paymentmodel.Customer) {
			plan, err := FindPlanByTitleAndInterval(StartingPlan, StartingInterval)
			So(err, ShouldBeNil)
			So(plan, ShouldNotBeNil)

			sub, err := CreateSubscription(c, plan)
			So(err, ShouldBeNil)

			err = CancelSubscription(c, sub)
			So(err, ShouldBeNil)

			Convey("When customer cancels subscription", func() {
				Convey("Then it should delete subscription in Stripe", func() {
					subParams := &stripe.SubParams{
						Customer: c.ProviderCustomerId,
					}

					_, err := stripeSub.Get(sub.ProviderSubscriptionId, subParams)
					So(err, ShouldNotBeNil)
				})

				Convey("Then it should update subsciption", func() {
					subscriptions, err := FindCustomerSubscriptions(c)
					So(err, ShouldBeNil)

					So(len(subscriptions), ShouldEqual, 1)

					current_subscription := subscriptions[0]
					So(current_subscription.State, ShouldEqual, SubscriptionStateCanceled)
				})
			})
		}),
	)
}
<|endoftext|>


<commit_before>package tlsg

import (
	"container/list"
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"
	"treeless/src/com"
	"treeless/src/com/udp"
)

//ServerGroup provides an access to a DB server group
type ServerGroup struct {
	sync.Mutex //All ServerGroup read/writes are mutex-protected
	//Database configuration
	NumChunks  int //Number of DB chunks
	Redundancy int //DB target redundancy
	//External status
	Servers map[string]*VirtualServer //Set of all DB servers
	chunks  []VirtualChunk            //Array of all DB chunks
	//Local properties
	stopped       bool
	localhost     *VirtualServer //Only for servers
	chunkStatus   []ChunkStatus  //Array of all DB chunks status
	heartbeatList *list.List     //List of pending heartbeats to request
	//Rebalance data
	useChannel         bool               //Send chunk updates to chunkUpdateChannel channel?
	chunkUpdateChannel chan *VirtualChunk //Each time a chunk status is updated the chunk is sent to the channel
}

const channelUpdateBufferSize = 1

var heartbeatTickDuration = time.Second * 1

type ChunkStatus int64

const (
	ChunkNotPresent ChunkStatus = iota
	ChunkSynched
	ChunkNotSynched
)

func (sg *ServerGroup) initChunks() {
	sg.chunks = make([]VirtualChunk, sg.NumChunks)
	sg.chunkStatus = make([]ChunkStatus, sg.NumChunks)

	//Add all chunks to the servergroup
	for i := 0; i < sg.NumChunks; i++ {
		sg.chunks[i].ID = i
		sg.chunks[i].Holders = make(map[*VirtualServer]bool)
	}
}

func (sg *ServerGroup) initLocalhost(port int) {
	localhost := tlcom.GetLocalIP() + ":" + fmt.Sprint(port)
	sg.AddServerToGroup(localhost)
	sg.localhost = sg.Servers[localhost]
}

//CreateServerGroup creates a new DB server group, without connecting to an existing group
func CreateServerGroup(numChunks int, port int, redundancy int) *ServerGroup {
	sg := new(ServerGroup)
	//DB configuration
	sg.NumChunks = numChunks
	sg.Redundancy = redundancy
	//Initialization
	sg.Servers = make(map[string]*VirtualServer)
	sg.initChunks()

	//Start daemons
	sg.StartRebalance()
	sg.startHeartbeatRequester()

	sg.initLocalhost(port)
	//Localhost holds all chunks
	sg.localhost.HeldChunks = make([]int, numChunks)
	for i := 0; i < numChunks; i++ {
		sg.localhost.HeldChunks[i] = i
		sg.chunks[i].Holders[sg.localhost] = true
		sg.chunkStatus[i] = ChunkSynched
	}
	return sg
}

func baseConnect(addr string) (*ServerGroup, error) {
	//Connect to the provided address
	c, err := tlcom.CreateConnection(addr)
	if err != nil {
		return nil, err
	}
	//Request access configuration information: server ips & DB numChunks
	b, err2 := c.GetAccessInfo()
	if err2 != nil {
		return nil, err2
	}
	c.Close()
	//Create a new server group by unmarshaling the provided information
	sg := new(ServerGroup)

	err = sg.Unmarshal(b)
	if err != nil {
		return nil, err
	}
	//Chunk initialization
	sg.initChunks()
	//Lookup for servers
	for _, s := range sg.Servers {
		//Request known chunks list
		aa, err := tlUDP.Request(s.Phy, time.Millisecond*50)
		if err == nil {
			s.LastHeartbeat = time.Now()
			s.HeldChunks = aa.KnownChunks
			for i := range s.HeldChunks {
				sg.chunks[i].Holders[s] = true
			}
		} else {
			log.Println("UDP initial request transmission error", err)
		}
	}
	return sg, nil
}

//ConnectAsClient connects to an existing server group as a client
func ConnectAsClient(addr string) (*ServerGroup, error) {
	sg, err := baseConnect(addr)
	if err != nil {
		return nil, err
	}
	//Launch UDP heartbeat requester
	sg.startHeartbeatRequester()
	return sg, nil
}

//Associate connects to an existing server group and adds this server to it
func Associate(destAddr string, localport int) (*ServerGroup, error) {
	sg, err := baseConnect(destAddr)
	if err != nil {
		return nil, err
	}
	//Start daemons
	sg.StartRebalance()
	sg.startHeartbeatRequester()

	//Add localhost
	sg.initLocalhost(localport)
	//Add to external servergroup instances
	//For each other server: add localhost
	for _, s := range sg.Servers {
		err := s.NeedConnection()
		if err != nil {
			log.Println("TCP transmission error when trying to add server", err)
			continue
		}
		err = s.Conn.AddServerToGroup(sg.localhost.Phy)
		if err != nil {
			panic(err)
		}
	}
	//All existing chunks must be checked
	for i := range sg.chunks {
		sg.chunkUpdateChannel <- &sg.chunks[i]
	}
	return sg, nil
}

func (sg *ServerGroup) startHeartbeatRequester() {
	l := list.New()
	sg.heartbeatList = l
	for _, s := range sg.Servers {
		l.PushFront(s)
	}
	sg.Lock()
	go func() {
		for {
			sg.Unlock()
			time.Sleep(heartbeatTickDuration)
			sg.Lock()

			if sg.stopped {
				return
			}

			s := l.Front().Value.(*VirtualServer)

			//Request known chunks list
			aa, err := tlUDP.Request(s.Phy, time.Millisecond*50)
			if err == nil {
				//Detect added servers
				for _, addr := range aa.KnownServers {
					_, exists := sg.Servers[addr]
					if !exists {
						sg.Servers[addr] = new(VirtualServer)
						sg.Servers[addr].Phy = addr
						l.PushFront(sg.Servers[addr])
					}
				}
				//Detect added chunks
				for _, c := range aa.KnownChunks {
					ok := false
					for _, c2 := range s.HeldChunks {
						if c == c2 {
							ok = true
							break
						}
					}
					if !ok && sg.useChannel {
						sg.chunkUpdateChannel <- &sg.chunks[c]
					}
				}
				//Detect forgotten chunks
				for _, c := range s.HeldChunks {
					ok := false
					for _, c2 := range aa.KnownChunks {
						if c == c2 {
							ok = true
							break
						}
					}
					if !ok && sg.useChannel {
						sg.chunkUpdateChannel <- &sg.chunks[c]
					}
				}

				s.HeldChunks = aa.KnownChunks
				//log.Println("held", s.Phy, aa)
				//Remove old chunks
				for c := range s.HeldChunks {
					delete(sg.chunks[c].Holders, s)
				}
				//Add new chunks
				for _, c := range s.HeldChunks {
					sg.chunks[c].Holders[s] = true
				}
				s.LastHeartbeat = time.Now()
			} else {
				log.Println("UDP request timeout. Server", s.Phy, "UDP error:", err)
			}
			//log.Println(sg)
			l.MoveToBack(l.Front())
		}
	}()
}

func (sg *ServerGroup) AddServerToGroup(addr string) error {
	sg.Lock()
	defer sg.Unlock()
	s, ok := sg.Servers[addr]
	if !ok {
		s = new(VirtualServer)
		sg.Servers[addr] = s
		s.Phy = addr
		s.LastHeartbeat = time.Now()
		sg.heartbeatList.PushBack(s)
	} else {
		s.LastHeartbeat = time.Now()
	}
	log.Println("Server", addr, "added")
	return nil
}

func oldServersRemover() {
	for {
		time.Sleep(time.Minute)
		//TODO: check for dead servers
		//remove them
	}
}

func (sg *ServerGroup) IsChunkPresent(id int) bool {
	return atomic.LoadInt64((*int64)(&sg.chunkStatus[id])) != 0
}

func (sg *ServerGroup) GetChunkHolders(chunkID int) []*VirtualServer {
	sg.Lock()
	defer sg.Unlock()
	c := sg.chunks[chunkID]
	holders := make([]*VirtualServer, len(c.Holders))
	i := 0
	for h := range c.Holders {
		holders[i] = h
		i++
	}
	return holders
}

func (sg *ServerGroup) Stop() {
	sg.Mutex.Lock()
	sg.stopped = true
	for _, s := range sg.Servers {
		if s.Conn != nil {
			s.Conn.Close()
			s.Conn = nil
		}
	}
	sg.Mutex.Unlock()
}

//String returns a human-readable representation of the server group
func (sg *ServerGroup) String() string {
	str := fmt.Sprint(len(sg.Servers)) + " servers:\n"
	t := time.Now()
	for _, s := range sg.Servers {
		str += "\t Address: " + s.Phy +
			"\n\t\tKnown chunks: " + fmt.Sprint(s.HeldChunks) + " Last heartbeat: " + (t.Sub(s.LastHeartbeat)).String() + "\n"
	}
	str += fmt.Sprint(sg.NumChunks) + " chunks:\n"
	for i := range sg.chunks {
		srv := ""
		for k := range sg.chunks[i].Holders {
			srv += "\n\t\t" + k.Phy
		}
		str += "\tChunk " + fmt.Sprint(i) + srv + "\n"
	}
	return str
}

//Marshal number of chunks and servers physical address
func (sg *ServerGroup) Marshal() ([]byte, error) {
	sg.Lock()
	defer sg.Unlock()
	return json.Marshal(sg)
}

//Unmarshal number of chunks and servers physical address
func (sg *ServerGroup) Unmarshal(b []byte) error {
	err := json.Unmarshal(b, sg)
	if err != nil {
		return err
	}
	for i := range sg.chunks {
		sg.chunks[i].Holders = make(map[*VirtualServer]bool)
	}
	return nil
}
<commit_msg>pequeños detalles sg<commit_after>package tlsg

import (
	"container/list"
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"time"
	"treeless/src/com"
	"treeless/src/com/udp"
)

//ServerGroup provides an access to a DB server group
type ServerGroup struct {
	sync.Mutex //All ServerGroup read/writes are mutex-protected
	//Database configuration
	NumChunks  int //Number of DB chunks
	Redundancy int //DB target redundancy
	//External status
	Servers map[string]*VirtualServer //Set of all DB servers
	chunks  []VirtualChunk            //Array of all DB chunks
	//Local properties
	stopped       bool
	localhost     *VirtualServer //Only for servers
	chunkStatus   []ChunkStatus  //Array of all DB chunks status
	heartbeatList *list.List     //List of pending heartbeats to request
	//Rebalance data
	useChannel         bool               //Send chunk updates to chunkUpdateChannel channel?
	chunkUpdateChannel chan *VirtualChunk //Each time a chunk status is updated the chunk is sent to the channel
}

const channelUpdateBufferSize = 1024

var heartbeatTickDuration = time.Second * 1

type ChunkStatus int64

const (
	ChunkNotPresent ChunkStatus = iota
	ChunkSynched
	ChunkNotSynched
)

func (sg *ServerGroup) initChunks() {
	sg.chunks = make([]VirtualChunk, sg.NumChunks)
	sg.chunkStatus = make([]ChunkStatus, sg.NumChunks)

	//Add all chunks to the servergroup
	for i := 0; i < sg.NumChunks; i++ {
		sg.chunks[i].ID = i
		sg.chunks[i].Holders = make(map[*VirtualServer]bool)
	}
}

func (sg *ServerGroup) initLocalhost(port int) {
	localhost := tlcom.GetLocalIP() + ":" + fmt.Sprint(port)
	s := new(VirtualServer)
	sg.Servers[localhost] = s
	s.Phy = localhost
	s.LastHeartbeat = time.Now()
	sg.localhost = sg.Servers[localhost]
}

//CreateServerGroup creates a new DB server group, without connecting to an existing group
func CreateServerGroup(numChunks int, port int, redundancy int) *ServerGroup {
	sg := new(ServerGroup)
	sg.Lock()
	defer sg.Unlock()
	//DB configuration
	sg.NumChunks = numChunks
	sg.Redundancy = redundancy
	//Initialization
	sg.Servers = make(map[string]*VirtualServer)
	sg.initChunks()

	//Start daemons
	sg.StartRebalance()
	sg.startHeartbeatRequester()

	sg.initLocalhost(port)
	//Localhost holds all chunks
	sg.localhost.HeldChunks = make([]int, numChunks)
	for i := 0; i < numChunks; i++ {
		sg.localhost.HeldChunks[i] = i
		sg.chunks[i].Holders[sg.localhost] = true
		sg.chunkStatus[i] = ChunkSynched
	}
	return sg
}

func baseConnect(addr string) (*ServerGroup, error) {
	//Connect to the provided address
	c, err := tlcom.CreateConnection(addr)
	if err != nil {
		return nil, err
	}
	//Request access configuration information: server ips & DB numChunks
	b, err2 := c.GetAccessInfo()
	if err2 != nil {
		return nil, err2
	}
	c.Close()
	//Create a new server group by unmarshaling the provided information
	sg := new(ServerGroup)
	err = sg.Unmarshal(b)
	if err != nil {
		return nil, err
	}
	//Chunk initialization
	sg.initChunks()
	//Lookup for servers
	for _, s := range sg.Servers {
		//Request known chunks list
		aa, err := tlUDP.Request(s.Phy, time.Millisecond*50)
		if err == nil {
			s.LastHeartbeat = time.Now()
			s.HeldChunks = aa.KnownChunks
			for i := range s.HeldChunks {
				sg.chunks[i].Holders[s] = true
			}
		} else {
			log.Println("UDP initial request transmission error", err)
		}
	}
	return sg, nil
}

//ConnectAsClient connects to an existing server group as a client
func ConnectAsClient(addr string) (*ServerGroup, error) {
	sg, err := baseConnect(addr)
	if err != nil {
		return nil, err
	}
	sg.Lock()
	defer sg.Unlock()
	//Launch UDP heartbeat requester
	sg.startHeartbeatRequester()
	return sg, nil
}

//Associate connects to an existing server group and adds this server to it
func Associate(destAddr string, localport int) (*ServerGroup, error) {
	sg, err := baseConnect(destAddr)
	if err != nil {
		return nil, err
	}
	sg.Lock()
	defer sg.Unlock()

	//Start daemons
	sg.StartRebalance()
	sg.startHeartbeatRequester()

	//Add localhost
	sg.initLocalhost(localport)
	//Add to external servergroup instances
	//For each other server: add localhost
	for _, s := range sg.Servers {
		if s == sg.localhost {
			continue
		}
		err := s.NeedConnection()
		if err != nil {
			log.Println("TCP transmission error when trying to add server", err)
			continue
		}
		err = s.Conn.AddServerToGroup(sg.localhost.Phy)
		if err != nil {
			panic(err)
		}
	}

	//All existing chunks must be checked
	for i := range sg.chunks {
		sg.Unlock()
		sg.chunkUpdateChannel <- &sg.chunks[i]
		sg.Lock()
	}

	return sg, nil
}

func (sg *ServerGroup) startHeartbeatRequester() {
	l := list.New()
	sg.heartbeatList = l
	for _, s := range sg.Servers {
		l.PushFront(s)
	}
	go func() {
		sg.Lock()
		for {
			sg.Unlock()
			time.Sleep(heartbeatTickDuration)
			sg.Lock()

			if sg.stopped {
				return
			}

			if l.Len() == 0 {
				continue
			}

			s := l.Front().Value.(*VirtualServer)

			//Request known chunks list
			aa, err := tlUDP.Request(s.Phy, time.Millisecond*50)
			if err == nil {
				//Detect added servers
				for _, addr := range aa.KnownServers {
					_, exists := sg.Servers[addr]
					if !exists {
						sg.Servers[addr] = new(VirtualServer)
						sg.Servers[addr].Phy = addr
						l.PushFront(sg.Servers[addr])
					}
				}
				//Detect added chunks
				for _, c := range aa.KnownChunks {
					ok := false
					for _, c2 := range s.HeldChunks {
						if c == c2 {
							ok = true
							break
						}
					}
					if !ok && sg.useChannel {
						sg.chunkUpdateChannel <- &sg.chunks[c]
					}
				}
				//Detect forgotten chunks
				for _, c := range s.HeldChunks {
					ok := false
					for _, c2 := range aa.KnownChunks {
						if c == c2 {
							ok = true
							break
						}
					}
					if !ok && sg.useChannel {
						//TODO LOCK UNLOCK
						sg.chunkUpdateChannel <- &sg.chunks[c]
					}
				}

				s.HeldChunks = aa.KnownChunks
				//log.Println("held", s.Phy, aa)
				//Remove old chunks
				for c := range s.HeldChunks {
					delete(sg.chunks[c].Holders, s)
				}
				//Add new chunks
				for _, c := range s.HeldChunks {
					sg.chunks[c].Holders[s] = true
				}
				s.LastHeartbeat = time.Now()
			} else {
				log.Println("UDP request timeout. Server", s.Phy, "UDP error:", err)
			}
			//log.Println(sg)
			l.MoveToBack(l.Front())
		}
	}()
}

func (sg *ServerGroup) AddServerToGroup(addr string) error {
	sg.Lock()
	defer sg.Unlock()
	s, ok := sg.Servers[addr]
	if !ok {
		s = new(VirtualServer)
		sg.Servers[addr] = s
		s.Phy = addr
		s.LastHeartbeat = time.Now()
		sg.heartbeatList.PushBack(s)
	} else {
		s.LastHeartbeat = time.Now()
	}
	log.Println("Server", addr, "added")
	return nil
}

func oldServersRemover() {
	for {
		time.Sleep(time.Minute)
		//TODO: check for dead servers
		//remove them
	}
}

func (sg *ServerGroup) IsChunkPresent(id int) bool {
	return atomic.LoadInt64((*int64)(&sg.chunkStatus[id])) != 0
}

func (sg *ServerGroup) GetChunkHolders(chunkID int) []*VirtualServer {
	sg.Lock()
	defer sg.Unlock()
	c := sg.chunks[chunkID]
	holders := make([]*VirtualServer, len(c.Holders))
	i := 0
	for h := range c.Holders {
		holders[i] = h
		i++
	}
	return holders
}

func (sg *ServerGroup) Stop() {
	sg.Mutex.Lock()
	sg.stopped = true
	for _, s := range sg.Servers {
		if s.Conn != nil {
			s.Conn.Close()
			s.Conn = nil
		}
	}
	sg.Mutex.Unlock()
}

//String returns a human-readable representation of the server group
func (sg *ServerGroup) String() string {
	str := fmt.Sprint(len(sg.Servers)) + " servers:\n"
	t := time.Now()
	for _, s := range sg.Servers {
		str += "\t Address: " + s.Phy +
			"\n\t\tKnown chunks: " + fmt.Sprint(s.HeldChunks) + " Last heartbeat: " + (t.Sub(s.LastHeartbeat)).String() + "\n"
	}
	str += fmt.Sprint(sg.NumChunks) + " chunks:\n"
	for i := range sg.chunks {
		srv := ""
		for k := range sg.chunks[i].Holders {
			srv += "\n\t\t" + k.Phy
		}
		str += "\tChunk " + fmt.Sprint(i) + srv + "\n"
	}
	return str
}

//Marshal number of chunks and servers physical address
func (sg *ServerGroup) Marshal() ([]byte, error) {
	sg.Lock()
	defer sg.Unlock()
	return json.Marshal(sg)
}

//Unmarshal number of chunks and servers physical address
func (sg *ServerGroup) Unmarshal(b []byte) error {
	err := json.Unmarshal(b, sg)
	if err != nil {
		return err
	}
	for i := range sg.chunks {
		sg.chunks[i].Holders = make(map[*VirtualServer]bool)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pod

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog"

	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/constants"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/conversion"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/metrics"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/reconciler"
)

const (
	// Default grace period in seconds
	minimumGracePeriodInSeconds = 30
)

var numStatusMissMatchedPods uint64
var numSpecMissMatchedPods uint64
var numUWMetaMissMatchedPods uint64

// StartPeriodChecker starts the period checker for data consistency check. Checker is
// blocking so should be called via a goroutine.
func (c *controller) StartPeriodChecker(stopCh <-chan struct{}) error {
	defer utilruntime.HandleCrash()

	if !cache.WaitForCacheSync(stopCh, c.podSynced) {
		return fmt.Errorf("failed to wait for caches to sync before starting Pod checker")
	}

	// Start a loop to do periodic GC of unused(orphan) vNodes in tenant masters.
	go c.vNodeGCWorker(stopCh)

	// Start a loop to periodically check if pods keep consistency between super
	// master and tenant masters.
	wait.Until(c.checkPods, c.periodCheckerPeriod, stopCh)

	return nil
}

func (c *controller) vNodeGCWorker(stopCh <-chan struct{}) {
	klog.Infof("Start VNode GarbageCollector")
	wait.Until(c.vNodeGCDo, c.periodCheckerPeriod, stopCh)
}

type Candidate struct {
	cluster  string
	nodeName string
}

func (c *controller) vNodeGCDo() {
	candidates := func() []Candidate {
		c.Lock()
		defer c.Unlock()
		var candidates []Candidate
		for cluster, nodeMap := range c.clusterVNodeGCMap {
			for nodeName, status := range nodeMap {
				if status.Phase == VNodeQuiescing && metav1.Now().After(status.QuiesceStartTime.Add(constants.DefaultvNodeGCGracePeriod)) {
					c.clusterVNodeGCMap[cluster][nodeName] = VNodeGCStatus{
						QuiesceStartTime: status.QuiesceStartTime,
						Phase:            VNodeDeleting,
					}
					candidates = append(candidates, Candidate{cluster: cluster, nodeName: nodeName})
				} else if status.Phase == VNodeDeleting {
					candidates = append(candidates, Candidate{cluster: cluster, nodeName: nodeName})
				}
			}
		}
		return candidates
	}()

	if len(candidates) == 0 {
		return
	}

	wg := sync.WaitGroup{}
	for _, candidate := range candidates {
		wg.Add(1)
		go func(cluster, nodeName string) {
			defer wg.Done()
			c.deleteClusterVNode(cluster, nodeName)
		}(candidate.cluster, candidate.nodeName)
	}
	wg.Wait()
}

func (c *controller) deleteClusterVNode(cluster, nodeName string) {
	tenantClient, err := c.multiClusterPodController.GetClusterClient(cluster)
	if err != nil {
		klog.Infof("cluster is removed, clear clusterVNodeGCMap entry for cluster %s", cluster)
		c.Lock()
		delete(c.clusterVNodeGCMap, cluster)
		c.Unlock()
		return
	}
	opts := metav1.NewDeleteOptions(0)
	opts.PropagationPolicy = &constants.DefaultDeletionPolicy

	tenantClient.CoreV1().Nodes().Delete(nodeName, opts)
	// We need to double check here.
	if _, err := tenantClient.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}); err != nil {
		if !errors.IsNotFound(err) {
			// If we cannot get the state from tenant apiserver, retry
			return
		}
	}
	c.Lock()
	delete(c.clusterVNodeGCMap[cluster], nodeName)
	c.Unlock()
}

// checkPods checks to see if pods in super master informer cache and tenant master
// keep consistency.
func (c *controller) checkPods() {
	clusterNames := c.multiClusterPodController.GetClusterNames()
	if len(clusterNames) == 0 {
		klog.Infof("tenant masters has no clusters, give up period checker")
		return
	}
	defer metrics.RecordCheckerScanDuration("pod", time.Now())
	wg := sync.WaitGroup{}

	numStatusMissMatchedPods = 0
	numSpecMissMatchedPods = 0
	numUWMetaMissMatchedPods = 0

	for _, clusterName := range clusterNames {
		wg.Add(1)
		go func(clusterName string) {
			defer wg.Done()
			c.checkPodsOfTenantCluster(clusterName)
			c.checkNodesOfTenantCluster(clusterName)
		}(clusterName)
	}
	wg.Wait()

	pPods, err := c.podLister.List(labels.Everything())
	if err != nil {
		klog.Errorf("error listing pods from super master informer cache: %v", err)
		return
	}

	for _, pPod := range pPods {
		clusterName, vNamespace := conversion.GetVirtualOwner(pPod)
		if len(clusterName) == 0 || len(vNamespace) == 0 {
			continue
		}

		shouldDelete := false
		vPodObj, err := c.multiClusterPodController.Get(clusterName, vNamespace, pPod.Name)
		if errors.IsNotFound(err) && pPod.DeletionTimestamp == nil {
			shouldDelete = true
		}
		if err == nil {
			vPod := vPodObj.(*v1.Pod)
			if pPod.Annotations[constants.LabelUID] != string(vPod.UID) {
				shouldDelete = true
				klog.Warningf("Found pPod %s/%s delegated UID is different from tenant object.", pPod.Namespace, pPod.Name)
			} else {
				if !equality.Semantic.DeepEqual(vPod.Status, pPod.Status) {
					numStatusMissMatchedPods++
					klog.Warningf("status of pod %v/%v diff in super&tenant master", pPod.Namespace, pPod.Name)
				}
			}
		}
		if shouldDelete {
			if pPod.DeletionTimestamp != nil {
				// pPod is under deletion, waiting for UWS bock populate the pod status.
				continue
			}
			// vPod not found and pPod not under deletion, we need to delete pPod manually
			gracePeriod := int64(minimumGracePeriodInSeconds)
			if pPod.Spec.TerminationGracePeriodSeconds != nil {
				gracePeriod = *pPod.Spec.TerminationGracePeriodSeconds
			}
			deleteOptions := metav1.NewDeleteOptions(gracePeriod)
			deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pPod.UID))
			if err = c.client.Pods(pPod.Namespace).Delete(pPod.Name, deleteOptions); err != nil {
				klog.Errorf("error deleting pPod %v/%v in super master: %v", pPod.Namespace, pPod.Name, err)
			} else {
				metrics.CheckerRemedyStats.WithLabelValues("numDeletedOrphanSuperMasterPods").Inc()
			}
		}
	}

	metrics.CheckerMissMatchStats.WithLabelValues("numStatusMissMatchedPods").Set(float64(numStatusMissMatchedPods))
	metrics.CheckerMissMatchStats.WithLabelValues("numSpecMissMatchedPods").Set(float64(numSpecMissMatchedPods))
	metrics.CheckerMissMatchStats.WithLabelValues("numUWMetaMissMatchedPods").Set(float64(numUWMetaMissMatchedPods))
}

// checkPodsOfTenantCluster checks to see if pods in specific cluster keeps consistency.
func (c *controller) checkPodsOfTenantCluster(clusterName string) {
	listObj, err := c.multiClusterPodController.List(clusterName)
	if err != nil {
		klog.Errorf("error listing pods from cluster %s informer cache: %v", clusterName, err)
		return
	}
	klog.V(4).Infof("check pods consistency in cluster %s", clusterName)
	podList := listObj.(*v1.PodList)
	for i, vPod := range podList.Items {
		if vPod.Spec.NodeName != "" && !isPodScheduled(&vPod) {
			// We should skip pods with NodeName set in the spec
			continue
		}
		targetNamespace := conversion.ToSuperMasterNamespace(clusterName, vPod.Namespace)
		pPod, err := c.podLister.Pods(targetNamespace).Get(vPod.Name)
		if errors.IsNotFound(err) {
			// pPod not found and vPod is under deletion, we need to delete vPod manually
			if vPod.DeletionTimestamp != nil {
				client, err := c.multiClusterPodController.GetClusterClient(clusterName)
				if err != nil {
					klog.Errorf("error getting cluster %s clientset: %v", clusterName, err)
					continue
				}
				// since pPod not found in super master, we can force delete vPod
				deleteOptions := metav1.NewDeleteOptions(0)
				deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(vPod.UID))
				if err = client.CoreV1().Pods(vPod.Namespace).Delete(vPod.Name, deleteOptions); err != nil {
					klog.Errorf("error deleting pod %v/%v in cluster %s: %v", vPod.Namespace, vPod.Name, clusterName, err)
				} else if vPod.Spec.NodeName != "" && isPodScheduled(&vPod) {
					c.updateClusterVNodePodMap(clusterName, vPod.Spec.NodeName, string(vPod.UID), reconciler.DeleteEvent)
				}
			} else {
				// pPod not found and vPod still exists, we need to create pPod again
				if err := c.multiClusterPodController.RequeueObject(clusterName, &podList.Items[i], reconciler.AddEvent); err != nil {
					klog.Errorf("error requeue vpod %v/%v in cluster %s: %v", vPod.Namespace, vPod.Name, clusterName, err)
				} else {
					metrics.CheckerRemedyStats.WithLabelValues("numRequeuedTenantPods").Inc()
				}
			}
			continue
		}

		if err != nil {
			klog.Errorf("error getting pPod %s/%s from super master cache: %v", targetNamespace, vPod.Name, err)
			continue
		}
		if pPod.Annotations[constants.LabelUID] != string(vPod.UID) {
			klog.Errorf("Found pPod %s/%s delegated UID is different from tenant object.", targetNamespace, pPod.Name)
			continue
		}

		spec, err := c.multiClusterPodController.GetSpec(clusterName)
		if err != nil {
			klog.Errorf("fail to get cluster spec : %s", clusterName)
			continue
		}
		updatedPod := conversion.Equality(spec).CheckPodEquality(pPod, &podList.Items[i])
		if updatedPod != nil {
			atomic.AddUint64(&numSpecMissMatchedPods, 1)
			klog.Warningf("spec of pod %v/%v diff in super&tenant master", vPod.Namespace, vPod.Name)
			if assignedPod(pPod) {
				c.enqueuePod(pPod)
			}
		}

		updatedMeta := conversion.Equality(spec).CheckUWObjectMetaEquality(&pPod.ObjectMeta, &podList.Items[i].ObjectMeta)
		if updatedMeta != nil {
			atomic.AddUint64(&numUWMetaMissMatchedPods, 1)
			klog.Warningf("UWObjectMeta of pod %v/%v diff in super&tenant master", vPod.Namespace, vPod.Name)
			if assignedPod(pPod) {
				c.enqueuePod(pPod)
			}
		}
	}
}

// checkNodesOfTenantCluster checks if any orphan vNode is missed in c.clusterVNodeGCMap, which can happen if syncer
// is restarted.
// Note that this method can be expensive since it cannot leverage pod mccontroller informer cache. The List query
// goes to tenant master directly. If this method causes performance issue, we should consider moving it to another
// periodic thread with a larger check interval.
func (c *controller) checkNodesOfTenantCluster(clusterName string) {
	listObj, err := c.multiClusterPodController.ListByObjectType(clusterName, &v1.Node{})
	if err != nil {
		klog.Errorf("failed to list vNode from cluster %s config: %v", clusterName, err)
		return
	}
	nodeList := listObj.(*v1.NodeList)
	for _, vNode := range nodeList.Items {
		if vNode.Labels[constants.LabelVirtualNode] != "true" {
			continue
		}
		func() {
			c.Lock()
			defer c.Unlock()
			if _, exist := c.clusterVNodePodMap[clusterName]; exist {
				if _, exist := c.clusterVNodePodMap[clusterName][vNode.Name]; exist {
					// Active Node
					return
				}
			}
			if _, exist := c.clusterVNodeGCMap[clusterName]; exist {
				if _, exist := c.clusterVNodeGCMap[clusterName][vNode.Name]; exist {
					// In GC list already
					return
				}
			}
			klog.Infof("find an orphan vNode %s missing in GC list in cluster %s", vNode.Name, clusterName)
			c.addToClusterVNodeGCMap(clusterName, vNode.Name)
		}()
	}
}
<commit_msg>requeue pPod if status diff from vPod<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pod

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog"

	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/constants"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/conversion"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/metrics"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/syncer/reconciler"
)

const (
	// Default grace period in seconds
	minimumGracePeriodInSeconds = 30
)

var numStatusMissMatchedPods uint64
var numSpecMissMatchedPods uint64
var numUWMetaMissMatchedPods uint64

// StartPeriodChecker starts the period checker for data consistency check. Checker is
// blocking so should be called via a goroutine.
func (c *controller) StartPeriodChecker(stopCh <-chan struct{}) error {
	defer utilruntime.HandleCrash()

	if !cache.WaitForCacheSync(stopCh, c.podSynced) {
		return fmt.Errorf("failed to wait for caches to sync before starting Pod checker")
	}

	// Start a loop to do periodic GC of unused(orphan) vNodes in tenant masters.
	go c.vNodeGCWorker(stopCh)

	// Start a loop to periodically check if pods keep consistency between super
	// master and tenant masters.
	wait.Until(c.checkPods, c.periodCheckerPeriod, stopCh)

	return nil
}

func (c *controller) vNodeGCWorker(stopCh <-chan struct{}) {
	klog.Infof("Start VNode GarbageCollector")
	wait.Until(c.vNodeGCDo, c.periodCheckerPeriod, stopCh)
}

type Candidate struct {
	cluster  string
	nodeName string
}

func (c *controller) vNodeGCDo() {
	candidates := func() []Candidate {
		c.Lock()
		defer c.Unlock()
		var candidates []Candidate
		for cluster, nodeMap := range c.clusterVNodeGCMap {
			for nodeName, status := range nodeMap {
				if status.Phase == VNodeQuiescing && metav1.Now().After(status.QuiesceStartTime.Add(constants.DefaultvNodeGCGracePeriod)) {
					c.clusterVNodeGCMap[cluster][nodeName] = VNodeGCStatus{
						QuiesceStartTime: status.QuiesceStartTime,
						Phase:            VNodeDeleting,
					}
					candidates = append(candidates, Candidate{cluster: cluster, nodeName: nodeName})
				} else if status.Phase == VNodeDeleting {
					candidates = append(candidates, Candidate{cluster: cluster, nodeName: nodeName})
				}
			}
		}
		return candidates
	}()

	if len(candidates) == 0 {
		return
	}

	wg := sync.WaitGroup{}
	for _, candidate := range candidates {
		wg.Add(1)
		go func(cluster, nodeName string) {
			defer wg.Done()
			c.deleteClusterVNode(cluster, nodeName)
		}(candidate.cluster, candidate.nodeName)
	}
	wg.Wait()
}

func (c *controller) deleteClusterVNode(cluster, nodeName string) {
	tenantClient, err := c.multiClusterPodController.GetClusterClient(cluster)
	if err != nil {
		klog.Infof("cluster is removed, clear clusterVNodeGCMap entry for cluster %s", cluster)
		c.Lock()
		delete(c.clusterVNodeGCMap, cluster)
		c.Unlock()
		return
	}
	opts := metav1.NewDeleteOptions(0)
	opts.PropagationPolicy = &constants.DefaultDeletionPolicy

	tenantClient.CoreV1().Nodes().Delete(nodeName, opts)
	// We need to double check here.
	if _, err := tenantClient.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}); err != nil {
		if !errors.IsNotFound(err) {
			// If we cannot get the state from tenant apiserver, retry
			return
		}
	}
	c.Lock()
	delete(c.clusterVNodeGCMap[cluster], nodeName)
	c.Unlock()
}

// checkPods checks to see if pods in super master informer cache and tenant master
// keep consistency.
func (c *controller) checkPods() {
	clusterNames := c.multiClusterPodController.GetClusterNames()
	if len(clusterNames) == 0 {
		klog.Infof("tenant masters has no clusters, give up period checker")
		return
	}
	defer metrics.RecordCheckerScanDuration("pod", time.Now())
	wg := sync.WaitGroup{}

	numStatusMissMatchedPods = 0
	numSpecMissMatchedPods = 0
	numUWMetaMissMatchedPods = 0

	for _, clusterName := range clusterNames {
		wg.Add(1)
		go func(clusterName string) {
			defer wg.Done()
			c.checkPodsOfTenantCluster(clusterName)
			c.checkNodesOfTenantCluster(clusterName)
		}(clusterName)
	}
	wg.Wait()

	pPods, err := c.podLister.List(labels.Everything())
	if err != nil {
		klog.Errorf("error listing pods from super master informer cache: %v", err)
		return
	}

	for _, pPod := range pPods {
		clusterName, vNamespace := conversion.GetVirtualOwner(pPod)
		if len(clusterName) == 0 || len(vNamespace) == 0 {
			continue
		}

		shouldDelete := false
		vPodObj, err := c.multiClusterPodController.Get(clusterName, vNamespace, pPod.Name)
		if errors.IsNotFound(err) && pPod.DeletionTimestamp == nil {
			shouldDelete = true
		}
		if err == nil {
			vPod := vPodObj.(*v1.Pod)
			if pPod.Annotations[constants.LabelUID] != string(vPod.UID) {
				shouldDelete = true
				klog.Warningf("Found pPod %s/%s delegated UID is different from tenant object.", pPod.Namespace, pPod.Name)
			} else {
				if !equality.Semantic.DeepEqual(vPod.Status, pPod.Status) {
					numStatusMissMatchedPods++
					klog.Warningf("status of pod %v/%v diff in super&tenant master", pPod.Namespace, pPod.Name)
					if assignedPod(pPod) {
						c.enqueuePod(pPod)
					}
				}
			}
		}
		if shouldDelete {
			if pPod.DeletionTimestamp != nil {
				// pPod is under deletion, waiting for UWS bock populate the pod status.
				continue
			}
			// vPod not found and pPod not under deletion, we need to delete pPod manually
			gracePeriod := int64(minimumGracePeriodInSeconds)
			if pPod.Spec.TerminationGracePeriodSeconds != nil {
				gracePeriod = *pPod.Spec.TerminationGracePeriodSeconds
			}
			deleteOptions := metav1.NewDeleteOptions(gracePeriod)
			deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pPod.UID))
			if err = c.client.Pods(pPod.Namespace).Delete(pPod.Name, deleteOptions); err != nil {
				klog.Errorf("error deleting pPod %v/%v in super master: %v", pPod.Namespace, pPod.Name, err)
			} else {
				metrics.CheckerRemedyStats.WithLabelValues("numDeletedOrphanSuperMasterPods").Inc()
			}
		}
	}

	metrics.CheckerMissMatchStats.WithLabelValues("numStatusMissMatchedPods").Set(float64(numStatusMissMatchedPods))
	metrics.CheckerMissMatchStats.WithLabelValues("numSpecMissMatchedPods").Set(float64(numSpecMissMatchedPods))
	metrics.CheckerMissMatchStats.WithLabelValues("numUWMetaMissMatchedPods").Set(float64(numUWMetaMissMatchedPods))
}

// checkPodsOfTenantCluster checks to see if pods in specific cluster keeps consistency.
func (c *controller) checkPodsOfTenantCluster(clusterName string) {
	listObj, err := c.multiClusterPodController.List(clusterName)
	if err != nil {
		klog.Errorf("error listing pods from cluster %s informer cache: %v", clusterName, err)
		return
	}
	klog.V(4).Infof("check pods consistency in cluster %s", clusterName)
	podList := listObj.(*v1.PodList)
	for i, vPod := range podList.Items {
		if vPod.Spec.NodeName != "" && !isPodScheduled(&vPod) {
			// We should skip pods with NodeName set in the spec
			continue
		}
		targetNamespace := conversion.ToSuperMasterNamespace(clusterName, vPod.Namespace)
		pPod, err := c.podLister.Pods(targetNamespace).Get(vPod.Name)
		if errors.IsNotFound(err) {
			// pPod not found and vPod is under deletion, we need to delete vPod manually
			if vPod.DeletionTimestamp != nil {
				client, err := c.multiClusterPodController.GetClusterClient(clusterName)
				if err != nil {
					klog.Errorf("error getting cluster %s clientset: %v", clusterName, err)
					continue
				}
				// since pPod not found in super master, we can force delete vPod
				deleteOptions := metav1.NewDeleteOptions(0)
				deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(vPod.UID))
				if err = client.CoreV1().Pods(vPod.Namespace).Delete(vPod.Name, deleteOptions); err != nil {
					klog.Errorf("error deleting pod %v/%v in cluster %s: %v", vPod.Namespace, vPod.Name, clusterName, err)
				} else if vPod.Spec.NodeName != "" && isPodScheduled(&vPod) {
					c.updateClusterVNodePodMap(clusterName, vPod.Spec.NodeName, string(vPod.UID), reconciler.DeleteEvent)
				}
			} else {
				// pPod not found and vPod still exists, we need to create pPod again
				if err := c.multiClusterPodController.RequeueObject(clusterName, &podList.Items[i], reconciler.AddEvent); err != nil {
					klog.Errorf("error requeue vpod %v/%v in cluster %s: %v", vPod.Namespace, vPod.Name, clusterName, err)
				} else {
					metrics.CheckerRemedyStats.WithLabelValues("numRequeuedTenantPods").Inc()
				}
			}
			continue
		}

		if err != nil {
			klog.Errorf("error getting pPod %s/%s from super master cache: %v", targetNamespace, vPod.Name, err)
			continue
		}
		if pPod.Annotations[constants.LabelUID] != string(vPod.UID) {
			klog.Errorf("Found pPod %s/%s delegated UID is different from tenant object.", targetNamespace, pPod.Name)
			continue
		}

		spec, err := c.multiClusterPodController.GetSpec(clusterName)
		if err != nil {
			klog.Errorf("fail to get cluster spec : %s", clusterName)
			continue
		}
		updatedPod := conversion.Equality(spec).CheckPodEquality(pPod, &podList.Items[i])
		if updatedPod != nil {
			atomic.AddUint64(&numSpecMissMatchedPods, 1)
			klog.Warningf("spec of pod %v/%v diff in super&tenant master", vPod.Namespace, vPod.Name)
			if assignedPod(pPod) {
				c.enqueuePod(pPod)
			}
		}

		updatedMeta := conversion.Equality(spec).CheckUWObjectMetaEquality(&pPod.ObjectMeta, &podList.Items[i].ObjectMeta)
		if updatedMeta != nil {
			atomic.AddUint64(&numUWMetaMissMatchedPods, 1)
			klog.Warningf("UWObjectMeta of pod %v/%v diff in super&tenant master", vPod.Namespace, vPod.Name)
			if assignedPod(pPod) {
				c.enqueuePod(pPod)
			}
		}
	}
}

// checkNodesOfTenantCluster checks if any orphan vNode is missed in c.clusterVNodeGCMap, which can happen if syncer
// is restarted.
// Note that this method can be expensive since it cannot leverage pod mccontroller informer cache. The List query
// goes to tenant master directly. If this method causes performance issue, we should consider moving it to another
// periodic thread with a larger check interval.
func (c *controller) checkNodesOfTenantCluster(clusterName string) {
	listObj, err := c.multiClusterPodController.ListByObjectType(clusterName, &v1.Node{})
	if err != nil {
		klog.Errorf("failed to list vNode from cluster %s config: %v", clusterName, err)
		return
	}
	nodeList := listObj.(*v1.NodeList)
	for _, vNode := range nodeList.Items {
		if vNode.Labels[constants.LabelVirtualNode] != "true" {
			continue
		}
		func() {
			c.Lock()
			defer c.Unlock()
			if _, exist := c.clusterVNodePodMap[clusterName]; exist {
				if _, exist := c.clusterVNodePodMap[clusterName][vNode.Name]; exist {
					// Active Node
					return
				}
			}
			if _, exist := c.clusterVNodeGCMap[clusterName]; exist {
				if _, exist := c.clusterVNodeGCMap[clusterName][vNode.Name]; exist {
					// In GC list already
					return
				}
			}
			klog.Infof("find an orphan vNode %s missing in GC list in cluster %s", vNode.Name, clusterName)
			c.addToClusterVNodeGCMap(clusterName, vNode.Name)
		}()
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2015 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2015 Andrii Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "fmt"
    "strings"

    "sippy/conf"
    "sippy/sdp"
    "sippy/types"
)

type msgBody struct {
    mtype                   string
    parsed_body             sippy_types.ParsedMsgBody
    string_content          string
    needs_update    bool
}

func NewMsgBody(content, mtype string) *msgBody {
    return &msgBody{
        mtype                   : mtype,
        parsed_body             : nil,
        string_content          : content,
        needs_update            : true,
    }
}

type genericMsgBody struct {
    body string
}

func newGenericMsgBody(body string) *genericMsgBody {
    return &genericMsgBody{ body }
}

func (self *genericMsgBody) String() string {
    return self.body
}

func (self *genericMsgBody) LocalStr(*sippy_conf.HostPort) string {
    return self.body
}

func (self *genericMsgBody) GetCopy() sippy_types.ParsedMsgBody {
    return &genericMsgBody{ self.body }
}

func (self *genericMsgBody) SetCHeaderAddr(string) {
    // NO OP
}

func (self *genericMsgBody) GetSections() []*sippy_sdp.SdpMediaDescription {
    return make([]*sippy_sdp.SdpMediaDescription, 0)
}

func (self *genericMsgBody) SetSections([]*sippy_sdp.SdpMediaDescription) {
    // NO OP
}

func (self *genericMsgBody) RemoveSection(int) {
    // NO OP
}

func (self *genericMsgBody) GetOHeader() *sippy_sdp.SdpOrigin {
    return nil
}

func (self *genericMsgBody) SetOHeader(*sippy_sdp.SdpOrigin) {
    // NO OP
}

func (self *genericMsgBody) AppendAHeader(string) {
    // NO OP
}

func (self *msgBody) GetParsedBody() (sippy_types.ParsedMsgBody, error) {
    if self.parsed_body == nil {
        err := self.parse()
        if err != nil {
            return nil, err
        }
    }
    return self.parsed_body, nil
}

func (self *msgBody) parse() error {
    self.parsed_body = newGenericMsgBody(self.string_content)
    if strings.HasPrefix(self.mtype, "multipart/mixed;") {
        arr := strings.SplitN(self.mtype, ";", 2)
        mtheaders := arr[1]
        var mth_boundary *string = nil
        for _, s := range strings.Split(mtheaders, ";") {
            arr = strings.SplitN(s, "=", 2)
            if arr[0] == "boundary" && len(arr) == 2 {
                mth_boundary = &arr[1]
                break
            }
        }
        if mth_boundary == nil {
            return fmt.Errorf("Error parsing the multipart message")
        }
        boundary := "--" + *mth_boundary
        for _, subsection := range strings.Split(self.string_content, boundary) {
            subsection = strings.TrimSpace(subsection)
            if subsection == "" { continue }
            boff, bdel := -1, ""
            for _, bdel = range []string{ "\r\n\r\n", "\r\r", "\n\n" } {
                boff = strings.Index(subsection, bdel)
                if boff != -1 {
                    break
                }
            }
            if boff == -1 {
                continue
            }
            mbody := subsection[boff + len(bdel):]
            mtype := ""
            for _, line := range strings.FieldsFunc(subsection[:boff], func(c rune) bool { return c == '\n' || c == '\r' }) {
                tmp := strings.ToLower(strings.TrimSpace(line))
                if strings.HasPrefix(tmp, "content-type:") {
                    arr = strings.SplitN(tmp, ":", 2)
                    mtype = strings.TrimSpace(arr[1])
                }
            }
            if mtype == "" {
                continue
            }
            if mtype == "application/sdp" {
                self.mtype = mtype
                self.string_content = mbody
                break
            }
        }
    }
    if self.mtype == "application/sdp" {
        parsed_body, err := ParseSdpBody(self.string_content)
        if err == nil {
            self.parsed_body = parsed_body
        } else {
            return fmt.Errorf("error parsing the SDP: %s", err.Error())
        }
    }
    return nil
}

func (self *msgBody) String() string {
    if self.parsed_body != nil {
        self.string_content = self.parsed_body.String()
    }
    return self.string_content
}

func (self *msgBody) LocalStr(local_hostport *sippy_conf.HostPort) string {
    if self.parsed_body != nil {
        return self.parsed_body.LocalStr(local_hostport)
    }
    return self.String()
}

func (self *msgBody) GetCopy() sippy_types.MsgBody {
    if self == nil {
        return nil
    }
    var parsed_body sippy_types.ParsedMsgBody
    if self.parsed_body != nil {
        parsed_body = self.parsed_body.GetCopy()
    } else {
        parsed_body = nil
    }
    return &msgBody{
        mtype                   : self.mtype,
        parsed_body             : parsed_body,
        string_content          : self.string_content,
        needs_update            : self.needs_update,
    }
}

func (self *msgBody) GetMtype() string {
    return self.mtype
}

func (self *msgBody) NeedsUpdate() bool {
    return self.needs_update
}

func (self *msgBody) SetNeedsUpdate(v bool) {
    self.needs_update = v
}

func (self *msgBody) AppendAHeader(hdr string) {
    if self.parsed_body != nil {
        self.parsed_body.AppendAHeader(hdr)
    } else {
        self.string_content += "a=" + hdr + "\r\n"
    }
}
<commit_msg>Reset the needs_update fields on copy.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2015 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2015 Andrii Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "fmt"
    "strings"

    "sippy/conf"
    "sippy/sdp"
    "sippy/types"
)

type msgBody struct {
    mtype                   string
    parsed_body             sippy_types.ParsedMsgBody
    string_content          string
    needs_update    bool
}

func NewMsgBody(content, mtype string) *msgBody {
    return &msgBody{
        mtype                   : mtype,
        parsed_body             : nil,
        string_content          : content,
        needs_update            : true,
    }
}

type genericMsgBody struct {
    body string
}

func newGenericMsgBody(body string) *genericMsgBody {
    return &genericMsgBody{ body }
}

func (self *genericMsgBody) String() string {
    return self.body
}

func (self *genericMsgBody) LocalStr(*sippy_conf.HostPort) string {
    return self.body
}

func (self *genericMsgBody) GetCopy() sippy_types.ParsedMsgBody {
    return &genericMsgBody{ self.body }
}

func (self *genericMsgBody) SetCHeaderAddr(string) {
    // NO OP
}

func (self *genericMsgBody) GetSections() []*sippy_sdp.SdpMediaDescription {
    return make([]*sippy_sdp.SdpMediaDescription, 0)
}

func (self *genericMsgBody) SetSections([]*sippy_sdp.SdpMediaDescription) {
    // NO OP
}

func (self *genericMsgBody) RemoveSection(int) {
    // NO OP
}

func (self *genericMsgBody) GetOHeader() *sippy_sdp.SdpOrigin {
    return nil
}

func (self *genericMsgBody) SetOHeader(*sippy_sdp.SdpOrigin) {
    // NO OP
}

func (self *genericMsgBody) AppendAHeader(string) {
    // NO OP
}

func (self *msgBody) GetParsedBody() (sippy_types.ParsedMsgBody, error) {
    if self.parsed_body == nil {
        err := self.parse()
        if err != nil {
            return nil, err
        }
    }
    return self.parsed_body, nil
}

func (self *msgBody) parse() error {
    self.parsed_body = newGenericMsgBody(self.string_content)
    if strings.HasPrefix(self.mtype, "multipart/mixed;") {
        arr := strings.SplitN(self.mtype, ";", 2)
        mtheaders := arr[1]
        var mth_boundary *string = nil
        for _, s := range strings.Split(mtheaders, ";") {
            arr = strings.SplitN(s, "=", 2)
            if arr[0] == "boundary" && len(arr) == 2 {
                mth_boundary = &arr[1]
                break
            }
        }
        if mth_boundary == nil {
            return fmt.Errorf("Error parsing the multipart message")
        }
        boundary := "--" + *mth_boundary
        for _, subsection := range strings.Split(self.string_content, boundary) {
            subsection = strings.TrimSpace(subsection)
            if subsection == "" { continue }
            boff, bdel := -1, ""
            for _, bdel = range []string{ "\r\n\r\n", "\r\r", "\n\n" } {
                boff = strings.Index(subsection, bdel)
                if boff != -1 {
                    break
                }
            }
            if boff == -1 {
                continue
            }
            mbody := subsection[boff + len(bdel):]
            mtype := ""
            for _, line := range strings.FieldsFunc(subsection[:boff], func(c rune) bool { return c == '\n' || c == '\r' }) {
                tmp := strings.ToLower(strings.TrimSpace(line))
                if strings.HasPrefix(tmp, "content-type:") {
                    arr = strings.SplitN(tmp, ":", 2)
                    mtype = strings.TrimSpace(arr[1])
                }
            }
            if mtype == "" {
                continue
            }
            if mtype == "application/sdp" {
                self.mtype = mtype
                self.string_content = mbody
                break
            }
        }
    }
    if self.mtype == "application/sdp" {
        parsed_body, err := ParseSdpBody(self.string_content)
        if err == nil {
            self.parsed_body = parsed_body
        } else {
            return fmt.Errorf("error parsing the SDP: %s", err.Error())
        }
    }
    return nil
}

func (self *msgBody) String() string {
    if self.parsed_body != nil {
        self.string_content = self.parsed_body.String()
    }
    return self.string_content
}

func (self *msgBody) LocalStr(local_hostport *sippy_conf.HostPort) string {
    if self.parsed_body != nil {
        return self.parsed_body.LocalStr(local_hostport)
    }
    return self.String()
}

func (self *msgBody) GetCopy() sippy_types.MsgBody {
    if self == nil {
        return nil
    }
    var parsed_body sippy_types.ParsedMsgBody
    if self.parsed_body != nil {
        parsed_body = self.parsed_body.GetCopy()
    } else {
        parsed_body = nil
    }
    return &msgBody{
        mtype                   : self.mtype,
        parsed_body             : parsed_body,
        string_content          : self.string_content,
        needs_update            : true,
    }
}

func (self *msgBody) GetMtype() string {
    return self.mtype
}

func (self *msgBody) NeedsUpdate() bool {
    return self.needs_update
}

func (self *msgBody) SetNeedsUpdate(v bool) {
    self.needs_update = v
}

func (self *msgBody) AppendAHeader(hdr string) {
    if self.parsed_body != nil {
        self.parsed_body.AppendAHeader(hdr)
    } else {
        self.string_content += "a=" + hdr + "\r\n"
    }
}
<|endoftext|>


<commit_before>// Package bloomskyStructure calls rest API Bloomsky, puts it in the structure and gives some function
package bloomskyStructure

import (
	"encoding/json"
	"fmt"
	"time"

	config "github.com/patrickalin/bloomsky-client-go/config"

	mylog "github.com/patrickalin/GoMyLog"
	rest "github.com/patrickalin/GoRest"
)

// generate by http://mervine.net/json2struct

// BloomskyStructure represent the structure of the JSON return by the API
type BloomskyStructure struct {
	UTC              float64                `json:"UTC"`
	CityName         string                 `json:"CityName"`
	Storm            BloomskyStormStructure `json:"Storm"`
	Searchable       bool                   `json:"Searchable"`
	DeviceName       string                 `json:"DeviceName"`
	RegisterTime     float64                `json:"RegisterTime"`
	DST              float64                `json:"DST"`
	BoundedPoint     string                 `json:"BoundedPoint"`
	LON              float64                `json:"LON"`
	Point            interface{}            `json:"Point"`
	VideoList        []string               `json:"VideoList"`
	VideoListC       []string               `json:"VideoList_C"`
	DeviceID         string                 `json:"DeviceID"`
	NumOfFollowers   float64                `json:"NumOfFollowers"`
	LAT              float64                `json:"LAT"`
	ALT              float64                `json:"ALT"`
	Data             BloomskyDataStructure  `json:"Data"`
	FullAddress      string                 `json:"FullAddress"`
	StreetName       string                 `json:"StreetName"`
	PreviewImageList []string               `json:"PreviewImageList"`
}

// BloomskyStormStructure represent the structure STORM of the JSON return by the API
type BloomskyStormStructure struct {
	UVIndex            string  `json:"UVIndex"`
	WindDirection      string  `json:"WindDirection"`
	RainDaily          float64 `json:"RainDaily"`
	WindGust           float64 `json:"WindGust"`
	SustainedWindSpeed float64 `json:"SustainedWindSpeed"`
	RainRate           float64 `json:"RainRate"`
	Rain               float64 `json:"24hRain"`
}

// BloomskyDataStructure represent the structure SKY of the JSON return by the API
type BloomskyDataStructure struct {
	Luminance   float64 `json:"Luminance"`
	Temperature float64 `json:"Temperature"`
	ImageURL    string  `json:"ImageURL"`
	TS          float64 `json:"TS"`
	Rain        bool    `json:"Rain"`
	Humidity    float64 `json:"Humidity"`
	Pressure    float64 `json:"Pressure"`
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
	Night       bool    `json:"Night"`
	UVIndex     float64 `json:"UVIndex"`
	ImageTS     float64 `json:"ImageTS"`
}

// bloomskyStructure is the interface bloomskyStructure
type bloomskyStructure interface {
	GetDeviceID() string
	GetSoftwareVersion() string
	GetAmbientTemperatureC() float64
	GetTargetTemperatureC() float64
	GetAmbientTemperatureF() float64
	GetTargetTemperatureF() float64
	GetHumidity() float64
	GetAway() string
	GetCity() string
	ShowPrettyAll() int
}

type bloomskyError struct {
	message error
	advice  string
}

func (e *bloomskyError) Error() string {
	return fmt.Sprintf("\n \t bloomskyError :> %s \n\t Advice :> %s", e.message, e.advice)
}

// ShowPrettyAll prints to the console the JSON
func (bloomskyInfo BloomskyStructure) ShowPrettyAll() int {
	out, err := json.Marshal(bloomskyInfo)
	if err != nil {
		fmt.Println("Error with parsing Json")
		mylog.Error.Fatal(err)
	}
	mylog.Trace.Printf("Decode:> \n %s \n\n", out)
	return 0
}

//GetTimeStamp returns the timestamp give by Bloomsky
func (bloomskyInfo BloomskyStructure) GetTimeStamp() time.Time {
	tm := time.Unix(int64(bloomskyInfo.Data.TS), 0)
	return tm
}

//GetCity returns the city name
func (bloomskyInfo BloomskyStructure) GetCity() string {
	return bloomskyInfo.CityName
}

//GetDeviceID returns the Device Id
func (bloomskyInfo BloomskyStructure) GetDeviceID() string {
	return bloomskyInfo.DeviceID
}

//GetNumOfFollowers returns the number of followers
func (bloomskyInfo BloomskyStructure) GetNumOfFollowers() int {
	return int(bloomskyInfo.NumOfFollowers)
}

//GetIndexUV returns the UV index from 1 to 11
func (bloomskyInfo BloomskyStructure) GetIndexUV() string {
	return bloomskyInfo.Storm.UVIndex
}

//IsNight returns true if it's the night
func (bloomskyInfo BloomskyStructure) IsNight() bool {
	return bloomskyInfo.Data.Night
}

//GetTemperatureFahrenheit returns temperature in Fahrenheit
func (bloomskyInfo BloomskyStructure) GetTemperatureFahrenheit() float64 {
	return bloomskyInfo.Data.Temperature
}

//GetTemperatureCelsius returns temperature in Celsius
func (bloomskyInfo BloomskyStructure) GetTemperatureCelsius() float64 {
	return ((bloomskyInfo.Data.Temperature - 32.00) * 5.00 / 9.00)
}

//GetHumidity returns hulidity %
func (bloomskyInfo BloomskyStructure) GetHumidity() float64 {
	return bloomskyInfo.Data.Humidity
}

//GetPressureHPa returns pressure in HPa
func (bloomskyInfo BloomskyStructure) GetPressureHPa() float64 {
	return (bloomskyInfo.Data.Pressure * 33.8638815)
}

//GetPressureInHg returns pressure in InHg
func (bloomskyInfo BloomskyStructure) GetPressureInHg() float64 {
	return bloomskyInfo.Data.Pressure
}

//GetWindDirection returns wind direction (N,S,W,E, ...)
func (bloomskyInfo BloomskyStructure) GetWindDirection() string {
	return bloomskyInfo.Storm.WindDirection
}

//GetWindGustMph returns Wind in Mph
func (bloomskyInfo BloomskyStructure) GetWindGustMph() float64 {
	return bloomskyInfo.Storm.WindGust
}

//GetWindGustMs returns Wind in Ms
func (bloomskyInfo BloomskyStructure) GetWindGustMs() float64 {
	return (bloomskyInfo.Storm.WindGust * 1.61)
}

//GetSustainedWindSpeedMph returns Sustained Wind Speed in Mph
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMph() float64 {
	return bloomskyInfo.Storm.SustainedWindSpeed
}

//GetSustainedWindSpeedMs returns Sustained Wind Speed in Ms
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMs() float64 {
	return (bloomskyInfo.Storm.SustainedWindSpeed * 1.61)
}

//IsRain returns true if it's rain
func (bloomskyInfo BloomskyStructure) IsRain() bool {
	return bloomskyInfo.Data.Rain
}

//GetRainDailyIn returns rain daily in In
func (bloomskyInfo BloomskyStructure) GetRainDailyIn() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainIn returns total rain in In
func (bloomskyInfo BloomskyStructure) GetRainIn() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateIn returns rain in In
func (bloomskyInfo BloomskyStructure) GetRainRateIn() float64 {
	return bloomskyInfo.Storm.RainRate
}

//GetRainDailyMm returns rain daily in mm
func (bloomskyInfo BloomskyStructure) GetRainDailyMm() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainMm returns total rain in mm
func (bloomskyInfo BloomskyStructure) GetRainMm() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateMm returns rain in mm
func (bloomskyInfo BloomskyStructure) GetRainRateMm() float64 {
	return bloomskyInfo.Storm.RainRate
}

/*
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
*/

// NewBloomsky calls bloomsky and get structurebloomsky
func NewBloomsky(oneConfig config.ConfigStructure) BloomskyStructure {

	var retry = 0
	var duration = time.Minute * 5

	// get body from Rest API
	mylog.Trace.Printf("Get from Rest bloomsky API")
	myRest := rest.MakeNew()

	b := []string{oneConfig.BloomskyAccessToken}

	var m map[string][]string
	m = make(map[string][]string)
	m["Authorization"] = b

	for retry < 5 {
		if err := myRest.GetWithHeaders(oneConfig.BloomskyURL, m); err != nil {
			mylog.Error.Println(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
			retry++
			time.Sleep(duration)
		} else {
			retry = 5
		}
	}

	body := myRest.GetBody()
	return NewBloomskyFromBody(body)
}

// NewBloomskyFromBody to test with String
func NewBloomskyFromBody(body []byte) BloomskyStructure {
	var bloomskyInfo []BloomskyStructure
	mylog.Trace.Printf("Unmarshal the response")
	if err := json.Unmarshal(body, &bloomskyInfo); err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with json to struct, problem in the struct ?"})
	}

	bloomskyInfo[0].ShowPrettyAll()

	return bloomskyInfo[0]
}
<commit_msg>Refactoring + web<commit_after>// Package bloomskyStructure calls rest API Bloomsky, puts it in the structure and gives some function
package bloomskyStructure

import (
	"encoding/json"
	"fmt"
	"time"

	config "github.com/patrickalin/bloomsky-client-go/config"

	mylog "github.com/patrickalin/GoMyLog"
	rest "github.com/patrickalin/GoRest"
)

// generate by http://mervine.net/json2struct

// BloomskyStructure represent the structure of the JSON return by the API
type BloomskyStructure struct {
	UTC              float64                `json:"UTC"`
	CityName         string                 `json:"CityName"`
	Storm            BloomskyStormStructure `json:"Storm"`
	Searchable       bool                   `json:"Searchable"`
	DeviceName       string                 `json:"DeviceName"`
	RegisterTime     float64                `json:"RegisterTime"`
	DST              float64                `json:"DST"`
	BoundedPoint     string                 `json:"BoundedPoint"`
	LON              float64                `json:"LON"`
	Point            interface{}            `json:"Point"`
	VideoList        []string               `json:"VideoList"`
	VideoListC       []string               `json:"VideoList_C"`
	DeviceID         string                 `json:"DeviceID"`
	NumOfFollowers   float64                `json:"NumOfFollowers"`
	LAT              float64                `json:"LAT"`
	ALT              float64                `json:"ALT"`
	Data             BloomskyDataStructure  `json:"Data"`
	FullAddress      string                 `json:"FullAddress"`
	StreetName       string                 `json:"StreetName"`
	PreviewImageList []string               `json:"PreviewImageList"`
}

// BloomskyStormStructure represent the structure STORM of the JSON return by the API
type BloomskyStormStructure struct {
	UVIndex            string  `json:"UVIndex"`
	WindDirection      string  `json:"WindDirection"`
	RainDaily          float64 `json:"RainDaily"`
	WindGust           float64 `json:"WindGust"`
	SustainedWindSpeed float64 `json:"SustainedWindSpeed"`
	RainRate           float64 `json:"RainRate"`
	Rain               float64 `json:"24hRain"`
}

// BloomskyDataStructure represent the structure SKY of the JSON return by the API
type BloomskyDataStructure struct {
	Luminance   float64 `json:"Luminance"`
	Temperature float64 `json:"Temperature"`
	ImageURL    string  `json:"ImageURL"`
	TS          float64 `json:"TS"`
	Rain        bool    `json:"Rain"`
	Humidity    float64 `json:"Humidity"`
	Pressure    float64 `json:"Pressure"`
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
	Night       bool    `json:"Night"`
	UVIndex     float64 `json:"UVIndex"`
	ImageTS     float64 `json:"ImageTS"`
}

// bloomskyStructure is the interface bloomskyStructure
type bloomskyStructure interface {
	GetDeviceID() string
	GetSoftwareVersion() string
	GetAmbientTemperatureC() float64
	GetTargetTemperatureC() float64
	GetAmbientTemperatureF() float64
	GetTargetTemperatureF() float64
	GetHumidity() float64
	GetAway() string
	GetCity() string
	ShowPrettyAll() int
}

type bloomskyError struct {
	message error
	advice  string
}

// ShowPrettyAll prints to the console the JSON
func (bloomskyInfo BloomskyStructure) ShowPrettyAll() {
	out, err := json.Marshal(bloomskyInfo)
	if err != nil {
		mylog.Error.Fatal(fmt.Errorf("Error with parsing Json"))
	}
	mylog.Trace.Printf("Decode:> \n %s \n\n", out)
}

//GetTimeStamp returns the timestamp give by Bloomsky
func (bloomskyInfo BloomskyStructure) GetTimeStamp() time.Time {
	tm := time.Unix(int64(bloomskyInfo.Data.TS), 0)
	return tm
}

//GetCity returns the city name
func (bloomskyInfo BloomskyStructure) GetCity() string {
	return bloomskyInfo.CityName
}

//GetDeviceID returns the Device Id
func (bloomskyInfo BloomskyStructure) GetDeviceID() string {
	return bloomskyInfo.DeviceID
}

//GetNumOfFollowers returns the number of followers
func (bloomskyInfo BloomskyStructure) GetNumOfFollowers() int {
	return int(bloomskyInfo.NumOfFollowers)
}

//GetIndexUV returns the UV index from 1 to 11
func (bloomskyInfo BloomskyStructure) GetIndexUV() string {
	return bloomskyInfo.Storm.UVIndex
}

//IsNight returns true if it's the night
func (bloomskyInfo BloomskyStructure) IsNight() bool {
	return bloomskyInfo.Data.Night
}

//GetTemperatureFahrenheit returns temperature in Fahrenheit
func (bloomskyInfo BloomskyStructure) GetTemperatureFahrenheit() float64 {
	return bloomskyInfo.Data.Temperature
}

//GetTemperatureCelsius returns temperature in Celsius
func (bloomskyInfo BloomskyStructure) GetTemperatureCelsius() float64 {
	return ((bloomskyInfo.Data.Temperature - 32.00) * 5.00 / 9.00)
}

//GetHumidity returns hulidity %
func (bloomskyInfo BloomskyStructure) GetHumidity() float64 {
	return bloomskyInfo.Data.Humidity
}

//GetPressureHPa returns pressure in HPa
func (bloomskyInfo BloomskyStructure) GetPressureHPa() float64 {
	return (bloomskyInfo.Data.Pressure * 33.8638815)
}

//GetPressureInHg returns pressure in InHg
func (bloomskyInfo BloomskyStructure) GetPressureInHg() float64 {
	return bloomskyInfo.Data.Pressure
}

//GetWindDirection returns wind direction (N,S,W,E, ...)
func (bloomskyInfo BloomskyStructure) GetWindDirection() string {
	return bloomskyInfo.Storm.WindDirection
}

//GetWindGustMph returns Wind in Mph
func (bloomskyInfo BloomskyStructure) GetWindGustMph() float64 {
	return bloomskyInfo.Storm.WindGust
}

//GetWindGustMs returns Wind in Ms
func (bloomskyInfo BloomskyStructure) GetWindGustMs() float64 {
	return (bloomskyInfo.Storm.WindGust * 1.61)
}

//GetSustainedWindSpeedMph returns Sustained Wind Speed in Mph
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMph() float64 {
	return bloomskyInfo.Storm.SustainedWindSpeed
}

//GetSustainedWindSpeedMs returns Sustained Wind Speed in Ms
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMs() float64 {
	return (bloomskyInfo.Storm.SustainedWindSpeed * 1.61)
}

//IsRain returns true if it's rain
func (bloomskyInfo BloomskyStructure) IsRain() bool {
	return bloomskyInfo.Data.Rain
}

//GetRainDailyIn returns rain daily in In
func (bloomskyInfo BloomskyStructure) GetRainDailyIn() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainIn returns total rain in In
func (bloomskyInfo BloomskyStructure) GetRainIn() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateIn returns rain in In
func (bloomskyInfo BloomskyStructure) GetRainRateIn() float64 {
	return bloomskyInfo.Storm.RainRate
}

//GetRainDailyMm returns rain daily in mm
func (bloomskyInfo BloomskyStructure) GetRainDailyMm() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainMm returns total rain in mm
func (bloomskyInfo BloomskyStructure) GetRainMm() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateMm returns rain in mm
func (bloomskyInfo BloomskyStructure) GetRainRateMm() float64 {
	return bloomskyInfo.Storm.RainRate
}

// NewBloomsky calls bloomsky and get structurebloomsky
func NewBloomsky(oneConfig config.Configuration) BloomskyStructure {

	var retry = 0
	var duration = time.Minute * 5

	// get body from Rest API
	mylog.Trace.Printf("Get from Rest bloomsky API")
	myRest := rest.MakeNew()

	b := []string{oneConfig.BloomskyAccessToken}

	var m map[string][]string
	m = make(map[string][]string)
	m["Authorization"] = b

	for retry < 5 {
		if err := myRest.GetWithHeaders(oneConfig.BloomskyURL, m); err != nil {
			mylog.Error.Println(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
			retry++
			time.Sleep(duration)
		} else {
			retry = 5
		}
	}

	body := myRest.GetBody()
	return NewBloomskyFromBody(body)
}

// NewBloomskyFromBody to test with String
func NewBloomskyFromBody(body []byte) BloomskyStructure {
	var bloomskyInfo []BloomskyStructure
	mylog.Trace.Printf("Unmarshal the response")
	if err := json.Unmarshal(body, &bloomskyInfo); err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with json to struct, problem in the struct ?"})
	}

	bloomskyInfo[0].ShowPrettyAll()

	return bloomskyInfo[0]
}
<|endoftext|>