<commit_before>package webserver

import (
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/gorilla/websocket"
	"github.com/monopole/mdrip/base"
	"github.com/monopole/mdrip/loader"
	"github.com/monopole/mdrip/model"
	"github.com/monopole/mdrip/program"
	"github.com/monopole/mdrip/tmux"
	"github.com/monopole/mdrip/util"
	"github.com/monopole/mdrip/webapp"
)

type myConn struct {
	conn    *websocket.Conn
	lastUse time.Time
}

func (c myConn) Write(bytes []byte) (n int, err error) {
	glog.Info("Attempting socket write.")
	c.lastUse = time.Now()
	err = c.conn.WriteMessage(websocket.TextMessage, bytes)
	if err != nil {
		glog.Error("bad socket write:", err)
		return 0, err
	}
	glog.Info("Socket seemed to work.")
	return len(bytes), nil
}

type Server struct {
	loader           *loader.Loader
	tutorial         model.Tutorial
	store            sessions.Store
	upgrader         websocket.Upgrader
	connections      map[webapp.TypeSessId]*myConn
	connReaperQuitCh chan bool
}

const (
	cookieName = "mdrip"
	keySessId  = "sessId"
)

// var keyAuth = securecookie.GenerateRandomKey(16)
var keyAuth = []byte("static-visible-secret")
var keyEncrypt = []byte(nil)

func NewServer(l *loader.Loader) (*Server, error) {
	s := sessions.NewCookieStore(keyAuth, keyEncrypt)
	s.Options = &sessions.Options{
		Domain:   "localhost",
		Path:     "/",
		MaxAge:   3600 * 8, // 8 hours
		HttpOnly: true,
	}
	result := &Server{
		l,
		nil,
		s,
		websocket.Upgrader{},
		make(map[webapp.TypeSessId]*myConn),
		make(chan bool),
	}
	go result.reapConnections()
	return result, nil
}

func getSessionId(s *sessions.Session) webapp.TypeSessId {
	if c, ok := s.Values[keySessId].(string); ok {
		return webapp.TypeSessId(c)
	}
	return ""
}

func assureSessionId(s *sessions.Session) webapp.TypeSessId {
	c := getSessionId(s)
	if c == "" {
		c = makeSessionId()
		s.Values[keySessId] = string(c)
	}
	return c
}

func makeSessionId() webapp.TypeSessId {
	b := make([]byte, 5)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return webapp.TypeSessId(fmt.Sprintf("%X", b))
}

func getSessionIdParam(n string, r *http.Request) (webapp.TypeSessId, error) {
	v := r.URL.Query().Get(n)
	if v == "" {
		return "", errors.New("no session Id")
	}
	return webapp.TypeSessId(v), nil
}

// Pull session Id out of request, create a socket connection,
// store connection in a map.  The block runner will attempt to
// find the connection and write to it, else fall back to its
// other behaviors.
func (ws *Server) openWebSocket(w http.ResponseWriter, r *http.Request) {
	sessId, err := getSessionIdParam("id", r)
	if err != nil {
		glog.Errorf("no session Id: %v", err)
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if c := ws.connections[sessId]; c != nil {
		glog.Info("Wut? session found: ", sessId)
		// Possibly the other side shutdown and restarted.
		// Close and make new one.
		c.conn.Close()
		ws.connections[sessId] = nil
	}
	c, err := ws.upgrader.Upgrade(w, r, nil)
	if err != nil {
		glog.Info("upgrade:", err)
		return
	}
	glog.Info("established websocket")
	go func() {
		_, message, err := c.ReadMessage()
		if err == nil {
			glog.Info("handshake: ", string(message))
		} else {
			glog.Info("websocket err:", err)
		}
	}()
	ws.connections[sessId] = &myConn{c, time.Now()}
}

func write500(w http.ResponseWriter, e error) {
	http.Error(w, e.Error(), http.StatusInternalServerError)
}

func (ws *Server) reload(w http.ResponseWriter, r *http.Request) {
	value := mux.Vars(r)["gitclone"]
	if len(value) < 1 {
		value = r.URL.Query().Get("q")
	}
	var t model.Tutorial
	var err error
	if len(value) > 0 {
		// Load data from new source.
		ds, err := base.NewDataSource([]string{value})
		if err != nil {
			http.Error(w,
				fmt.Sprintf("Bad value %s", value), http.StatusBadRequest)
			return
		}
		l := loader.NewLoader(ds)
		t, err = l.Load()
		if err != nil {
			http.Error(w,
				fmt.Sprintf("Unable to load from %s: %v", ds, err),
				http.StatusBadRequest)
			return
		}
		ws.loader = l
	} else {
		// reload from same source, presumably changed.
		t, err = ws.loader.Load()
		if err != nil {
			write500(w, err)
			return
		}
	}
	ws.tutorial = t
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func (ws *Server) showControlPage(w http.ResponseWriter, r *http.Request) {
	session, err := ws.store.Get(r, cookieName)
	if err != nil {
		write500(w, err)
		return
	}
	app := webapp.NewWebApp(assureSessionId(session), r.Host, ws.tutorial)
	err = session.Save(r, w)
	if err != nil {
		write500(w, err)
		return
	}
	if err := app.Render(w); err != nil {
		write500(w, err)
		return
	}
}

func (ws *Server) showDebugPage(w http.ResponseWriter, r *http.Request) {
	ws.tutorial.Accept(model.NewTutorialTxtPrinter(w))
	p := program.NewProgramFromTutorial(base.WildCardLabel, ws.tutorial)
	fmt.Fprintf(w, "\n\nfile count %d\n\n", len(p.Lessons()))
	for i, lesson := range p.Lessons() {
		fmt.Fprintf(w, "file %d: %s\n", i, lesson.Path())
		for j, b := range lesson.Blocks() {
			fmt.Fprintf(w, "  block %d, content: %s\n",
				j, util.SampleString(b.Code().String(), 50))
		}
	}
}

// Returns a writer one can write a code block to for execution.
// First tries to find a session socket.  Failing that, try to find
// a locally running instance of tmux.  Failing that, returns a
// writer that discards the code.
func (ws *Server) getCodeRunner(sessId webapp.TypeSessId) io.Writer {
	c := ws.connections[sessId]
	if c != nil {
		glog.Infof("Socket found for ID %v", sessId)
		return c
	}
	t := tmux.NewTmux(tmux.Path)
	if t.IsUp() {
		glog.Info("No socket, writing to local tmux.")
		return t
	}
	glog.Info("No sockets, tmux not up, discarding code.")
	return ioutil.Discard
}

func inRange(w http.ResponseWriter, name string, arg, n int) bool {
	if arg >= 0 || arg < n {
		return true
	}
	http.Error(w,
		fmt.Sprintf("%s %d out of range 0-%d",
			name, arg, n-1), http.StatusBadRequest)
	return false
}

func (ws *Server) makeBlockRunner() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ws.store.Get(r, cookieName)
		if err != nil {
			write500(w, err)
			return
		}
		sessId := assureSessionId(session)
		indexFile := getIntParam("fid", r, -1)
		glog.Info("fid = ", indexFile)
		indexBlock := getIntParam("bid", r, -1)
		glog.Info("bid = ", indexBlock)

		p := program.NewProgramFromTutorial(base.WildCardLabel, ws.tutorial)
		if !inRange(w, "fid", indexFile, len(p.Lessons())) {
			return
		}
		lesson := p.Lessons()[indexFile]
		if !inRange(w, "bid", indexBlock, len(lesson.Blocks())) {
			return
		}
		block := lesson.Blocks()[indexBlock]

		_, err = ws.getCodeRunner(sessId).Write(block.Code().Bytes())
		if err != nil {
			fmt.Fprintln(w, err)
			return
		}
		session.Values["file"] = strconv.Itoa(indexFile)
		session.Values["block"] = strconv.Itoa(indexBlock)
		err = session.Save(r, w)
		if err != nil {
			glog.Errorf("Unable to save session: %v", err)
		}
		fmt.Fprintln(w, "Ok")
	}
}

func (ws *Server) favicon(w http.ResponseWriter, r *http.Request) {
	util.Lissajous(w, 7, 3, 1)
}

func (ws *Server) image(w http.ResponseWriter, r *http.Request) {
	session, _ := ws.store.Get(r, cookieName)
	session.Save(r, w)
	util.Lissajous(w,
		getIntParam("s", r, 300),
		getIntParam("c", r, 30),
		getIntParam("n", r, 100))
}

func getIntParam(n string, r *http.Request, d int) int {
	v, err := strconv.Atoi(r.URL.Query().Get(n))
	if err != nil {
		return d
	}
	return v
}

func (ws *Server) quit(w http.ResponseWriter, r *http.Request) {
	close(ws.connReaperQuitCh)
	os.Exit(0)
}

const (
	maxConnectionIdleTime    = 10 * time.Minute
	connectionScanWaitPeriod = 5 * time.Minute
)

// Look for and close idle websockets.
func (ws *Server) closeStaleConnections() {
	for s, c := range ws.connections {
		if time.Since(c.lastUse) > maxConnectionIdleTime {
			glog.Info(
				"Time since last use in session %v exceeds %v; closing.",
				s, maxConnectionIdleTime)
			c.conn.Close()
		}
	}
}

// reapConnections periodically scans websockets for idleness.
// It also closes everything and quits scanning if quit signal received.
func (ws *Server) reapConnections() {
	for {
		ws.closeStaleConnections()
		select {
		case <-time.After(connectionScanWaitPeriod):
		case <-ws.connReaperQuitCh:
			glog.Info("Received quit, reaping all connections.")
			for _, c := range ws.connections {
				c.conn.Close()
			}
			return
		}
	}
}

// Serve offers an http service.
func (ws *Server) Serve(hostAndPort string) {
	r := mux.NewRouter()
	r.HandleFunc("/r", ws.reload)
	r.HandleFunc("/r/", ws.reload)
	r.HandleFunc("/r/{gitclone:.*}", ws.reload)
	r.HandleFunc("/runblock", ws.makeBlockRunner())
	r.HandleFunc("/debug", ws.showDebugPage)
	r.HandleFunc("/ws", ws.openWebSocket)
	r.HandleFunc("/favicon.ico", ws.favicon)
	r.HandleFunc("/image", ws.image)
	r.HandleFunc("/q", ws.quit)
	r.HandleFunc("/", ws.showControlPage)
	ws.tutorial, _ = ws.loader.Load()
	fmt.Println("Serving at " + hostAndPort)
	glog.Info("Serving at " + hostAndPort)
	glog.Fatal(http.ListenAndServe(hostAndPort, r))
}
<commit_msg>socketfix<commit_after>package webserver

import (
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/gorilla/websocket"
	"github.com/monopole/mdrip/base"
	"github.com/monopole/mdrip/loader"
	"github.com/monopole/mdrip/model"
	"github.com/monopole/mdrip/program"
	"github.com/monopole/mdrip/tmux"
	"github.com/monopole/mdrip/util"
	"github.com/monopole/mdrip/webapp"
)

type myConn struct {
	conn    *websocket.Conn
	lastUse time.Time
}

func (c myConn) Write(bytes []byte) (n int, err error) {
	glog.Info("Attempting socket write.")
	c.lastUse = time.Now()
	err = c.conn.WriteMessage(websocket.TextMessage, bytes)
	if err != nil {
		glog.Error("bad socket write:", err)
		return 0, err
	}
	glog.Info("Socket seemed to work.")
	return len(bytes), nil
}

type Server struct {
	loader           *loader.Loader
	tutorial         model.Tutorial
	store            sessions.Store
	upgrader         websocket.Upgrader
	connections      map[webapp.TypeSessId]*myConn
	connReaperQuitCh chan bool
}

const (
	cookieName = "mdrip"
	keySessId  = "sessId"
)

// var keyAuth = securecookie.GenerateRandomKey(16)
var keyAuth = []byte("static-visible-secret")
var keyEncrypt = []byte(nil)

func NewServer(l *loader.Loader) (*Server, error) {
	s := sessions.NewCookieStore(keyAuth, keyEncrypt)
	s.Options = &sessions.Options{
		Domain:   "localhost",
		Path:     "/",
		MaxAge:   3600 * 8, // 8 hours
		HttpOnly: true,
	}
	result := &Server{
		l,
		nil,
		s,
		websocket.Upgrader{},
		make(map[webapp.TypeSessId]*myConn),
		make(chan bool),
	}
	go result.reapConnections()
	return result, nil
}

func getSessionId(s *sessions.Session) webapp.TypeSessId {
	if c, ok := s.Values[keySessId].(string); ok {
		return webapp.TypeSessId(c)
	}
	return ""
}

func assureSessionId(s *sessions.Session) webapp.TypeSessId {
	c := getSessionId(s)
	if c == "" {
		c = makeSessionId()
		s.Values[keySessId] = string(c)
	}
	return c
}

func makeSessionId() webapp.TypeSessId {
	b := make([]byte, 5)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return webapp.TypeSessId(fmt.Sprintf("%X", b))
}

func getSessionIdParam(n string, r *http.Request) (webapp.TypeSessId, error) {
	v := r.URL.Query().Get(n)
	if v == "" {
		return "", errors.New("no session Id")
	}
	return webapp.TypeSessId(v), nil
}

// Pull session Id out of request, create a socket connection,
// store connection in a map.  The block runner will attempt to
// find the connection and write to it, else fall back to its
// other behaviors.
func (ws *Server) openWebSocket(w http.ResponseWriter, r *http.Request) {
	sessId, err := getSessionIdParam("id", r)
	if err != nil {
		glog.Errorf("no session Id: %v", err)
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if c := ws.connections[sessId]; c != nil {
		glog.Info("Wut? session found: ", sessId)
		// Possibly the other side shutdown and restarted.
		// Close and make new one.
		c.conn.Close()
		delete(ws.connections, sessId)
	}
	c, err := ws.upgrader.Upgrade(w, r, nil)
	if err != nil {
		glog.Info("upgrade:", err)
		return
	}
	glog.Info("established websocket")
	go func() {
		_, message, err := c.ReadMessage()
		if err == nil {
			glog.Info("handshake: ", string(message))
		} else {
			glog.Info("websocket err:", err)
		}
	}()
	ws.connections[sessId] = &myConn{c, time.Now()}
}

func write500(w http.ResponseWriter, e error) {
	http.Error(w, e.Error(), http.StatusInternalServerError)
}

func (ws *Server) reload(w http.ResponseWriter, r *http.Request) {
	value := mux.Vars(r)["gitclone"]
	if len(value) < 1 {
		value = r.URL.Query().Get("q")
	}
	var t model.Tutorial
	var err error
	if len(value) > 0 {
		// Load data from new source.
		ds, err := base.NewDataSource([]string{value})
		if err != nil {
			http.Error(w,
				fmt.Sprintf("Bad value %s", value), http.StatusBadRequest)
			return
		}
		l := loader.NewLoader(ds)
		t, err = l.Load()
		if err != nil {
			http.Error(w,
				fmt.Sprintf("Unable to load from %s: %v", ds, err),
				http.StatusBadRequest)
			return
		}
		ws.loader = l
	} else {
		// reload from same source, presumably changed.
		t, err = ws.loader.Load()
		if err != nil {
			write500(w, err)
			return
		}
	}
	ws.tutorial = t
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func (ws *Server) showControlPage(w http.ResponseWriter, r *http.Request) {
	session, err := ws.store.Get(r, cookieName)
	if err != nil {
		write500(w, err)
		return
	}
	app := webapp.NewWebApp(assureSessionId(session), r.Host, ws.tutorial)
	err = session.Save(r, w)
	if err != nil {
		write500(w, err)
		return
	}
	if err := app.Render(w); err != nil {
		write500(w, err)
		return
	}
}

func (ws *Server) showDebugPage(w http.ResponseWriter, r *http.Request) {
	ws.tutorial.Accept(model.NewTutorialTxtPrinter(w))
	p := program.NewProgramFromTutorial(base.WildCardLabel, ws.tutorial)
	fmt.Fprintf(w, "\n\nfile count %d\n\n", len(p.Lessons()))
	for i, lesson := range p.Lessons() {
		fmt.Fprintf(w, "file %d: %s\n", i, lesson.Path())
		for j, b := range lesson.Blocks() {
			fmt.Fprintf(w, "  block %d, content: %s\n",
				j, util.SampleString(b.Code().String(), 50))
		}
	}
}

// Returns a writer one can write a code block to for execution.
// First tries to find a session socket.  Failing that, try to find
// a locally running instance of tmux.  Failing that, returns a
// writer that discards the code.
func (ws *Server) getCodeRunner(sessId webapp.TypeSessId) io.Writer {
	c := ws.connections[sessId]
	if c != nil {
		glog.Infof("Socket found for ID %v", sessId)
		return c
	}
	t := tmux.NewTmux(tmux.Path)
	if t.IsUp() {
		glog.Info("No socket, writing to local tmux.")
		return t
	}
	glog.Info("No sockets, tmux not up, discarding code.")
	return ioutil.Discard
}

func (ws *Server) attemptTmuxWrite(b *program.BlockPgm) error {
	t := tmux.NewTmux(tmux.Path)
	if !t.IsUp() {
		return errors.New("No local tmux to write to.")
	}
	_, err := t.Write(b.Code().Bytes())
	return err
}

func inRange(w http.ResponseWriter, name string, arg, n int) bool {
	if arg >= 0 || arg < n {
		return true
	}
	http.Error(w,
		fmt.Sprintf("%s %d out of range 0-%d",
			name, arg, n-1), http.StatusBadRequest)
	return false
}

func (ws *Server) makeBlockRunner() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := ws.store.Get(r, cookieName)
		if err != nil {
			write500(w, err)
			return
		}
		sessId := assureSessionId(session)
		indexFile := getIntParam("fid", r, -1)
		glog.Info("fid = ", indexFile)
		indexBlock := getIntParam("bid", r, -1)
		glog.Info("bid = ", indexBlock)

		p := program.NewProgramFromTutorial(base.WildCardLabel, ws.tutorial)
		if !inRange(w, "fid", indexFile, len(p.Lessons())) {
			return
		}
		lesson := p.Lessons()[indexFile]
		if !inRange(w, "bid", indexBlock, len(lesson.Blocks())) {
			return
		}
		block := lesson.Blocks()[indexBlock]

		err = nil
		c := ws.connections[sessId]
		if c != nil {
			_, err := c.Write(block.Code().Bytes())
			if err != nil {
				glog.Info("socket write failed %v", err)
				delete(ws.connections, sessId)
			}
		}
		if c == nil || err != nil {
			err = ws.attemptTmuxWrite(block)
			if err != nil {
				glog.Info("tmux write failed %v", err)
				// nothing more to try
			}
		}

		session.Values["file"] = strconv.Itoa(indexFile)
		session.Values["block"] = strconv.Itoa(indexBlock)
		err = session.Save(r, w)
		if err != nil {
			glog.Errorf("Unable to save session: %v", err)
		}
		fmt.Fprintln(w, "Ok")
	}
}

func (ws *Server) favicon(w http.ResponseWriter, r *http.Request) {
	util.Lissajous(w, 7, 3, 1)
}

func (ws *Server) image(w http.ResponseWriter, r *http.Request) {
	session, _ := ws.store.Get(r, cookieName)
	session.Save(r, w)
	util.Lissajous(w,
		getIntParam("s", r, 300),
		getIntParam("c", r, 30),
		getIntParam("n", r, 100))
}

func getIntParam(n string, r *http.Request, d int) int {
	v, err := strconv.Atoi(r.URL.Query().Get(n))
	if err != nil {
		return d
	}
	return v
}

func (ws *Server) quit(w http.ResponseWriter, r *http.Request) {
	close(ws.connReaperQuitCh)
	os.Exit(0)
}

const (
	maxConnectionIdleTime    = 10 * time.Minute
	connectionScanWaitPeriod = 5 * time.Minute
)

// Look for and close idle websockets.
func (ws *Server) closeStaleConnections() {
	for s, c := range ws.connections {
		if time.Since(c.lastUse) > maxConnectionIdleTime {
			glog.Info(
				"Time since last use in session %v exceeds %v; closing.",
				s, maxConnectionIdleTime)
			c.conn.Close()
			delete(ws.connections, s)
		}
	}
}

// reapConnections periodically scans websockets for idleness.
// It also closes everything and quits scanning if quit signal received.
func (ws *Server) reapConnections() {
	for {
		ws.closeStaleConnections()
		select {
		case <-time.After(connectionScanWaitPeriod):
		case <-ws.connReaperQuitCh:
			glog.Info("Received quit, reaping all connections.")
			for s, c := range ws.connections {
				c.conn.Close()
				delete(ws.connections, s)
			}
			return
		}
	}
}

// Serve offers an http service.
func (ws *Server) Serve(hostAndPort string) {
	r := mux.NewRouter()
	r.HandleFunc("/r", ws.reload)
	r.HandleFunc("/r/", ws.reload)
	r.HandleFunc("/r/{gitclone:.*}", ws.reload)
	r.HandleFunc("/runblock", ws.makeBlockRunner())
	r.HandleFunc("/debug", ws.showDebugPage)
	r.HandleFunc("/ws", ws.openWebSocket)
	r.HandleFunc("/favicon.ico", ws.favicon)
	r.HandleFunc("/image", ws.image)
	r.HandleFunc("/q", ws.quit)
	r.HandleFunc("/", ws.showControlPage)
	ws.tutorial, _ = ws.loader.Load()
	fmt.Println("Serving at " + hostAndPort)
	glog.Info("Serving at " + hostAndPort)
	glog.Fatal(http.ListenAndServe(hostAndPort, r))
}
<|endoftext|>


<commit_before>// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgo

import (
	"testing"

	"github.com/gonum/blas"
	"github.com/gonum/lapack/testlapack"
)

var impl = Implementation{}

// blockedTranslate transforms some blocked C calls to be the unblocked algorithms
// for testing, as several of the unblocked algorithms are not defined by the C
// interface.
type blockedTranslate struct {
	Implementation
}

func TestDbdsqr(t *testing.T) {
	testlapack.DbdsqrTest(t, impl)
}

func (bl blockedTranslate) Dgebd2(m, n int, a []float64, lda int, d, e, tauQ, tauP, work []float64) {
	impl.Dgebrd(m, n, a, lda, d, e, tauQ, tauP, work, len(work))
}

func (bl blockedTranslate) Dorm2r(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64) {
	impl.Dormqr(side, trans, m, n, k, a, lda, tau, c, ldc, work, len(work))
}

func (bl blockedTranslate) Dorml2(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64) {
	impl.Dormlq(side, trans, m, n, k, a, lda, tau, c, ldc, work, len(work))
}

func (bl blockedTranslate) Dorg2r(m, n, k int, a []float64, lda int, tau, work []float64) {
	impl.Dorgqr(m, n, k, a, lda, tau, work, len(work))
}

func (bl blockedTranslate) Dorgl2(m, n, k int, a []float64, lda int, tau, work []float64) {
	impl.Dorglq(m, n, k, a, lda, tau, work, len(work))
}

func TestDlacn2(t *testing.T) {
	testlapack.Dlacn2Test(t, impl)
}

func TestDlacpy(t *testing.T) {
	testlapack.DlacpyTest(t, impl)
}

func TestDlange(t *testing.T) {
	testlapack.DlangeTest(t, impl)
}

func TestDlarfb(t *testing.T) {
	testlapack.DlarfbTest(t, impl)
}

func TestDlarfg(t *testing.T) {
	testlapack.DlarfgTest(t, impl)
}

func TestDlarft(t *testing.T) {
	testlapack.DlarftTest(t, impl)
}

func TestDlantr(t *testing.T) {
	testlapack.DlantrTest(t, impl)
}

func TestDlarfx(t *testing.T) {
	testlapack.DlarfxTest(t, impl)
}

func TestDpotrf(t *testing.T) {
	testlapack.DpotrfTest(t, impl)
}

func TestDgebak(t *testing.T) {
	testlapack.DgebakTest(t, impl)
}

func TestDgebal(t *testing.T) {
	testlapack.DgebalTest(t, impl)
}

func TestDgebd2(t *testing.T) {
	testlapack.Dgebd2Test(t, blockedTranslate{impl})
}

func TestDgecon(t *testing.T) {
	testlapack.DgeconTest(t, impl)
}

func TestDgeev(t *testing.T) {
	testlapack.DgeevTest(t, impl)
}

func TestDgehrd(t *testing.T) {
	testlapack.DgehrdTest(t, impl)
}

func TestDgelq2(t *testing.T) {
	testlapack.Dgelq2Test(t, impl)
}

func TestDgels(t *testing.T) {
	testlapack.DgelsTest(t, impl)
}

func TestDgelqf(t *testing.T) {
	testlapack.DgelqfTest(t, impl)
}

func TestDgeqr2(t *testing.T) {
	testlapack.Dgeqr2Test(t, impl)
}

func TestDgeqrf(t *testing.T) {
	testlapack.DgeqrfTest(t, impl)
}

func TestDgesvd(t *testing.T) {
	testlapack.DgesvdTest(t, impl)
}

func TestDgetf2(t *testing.T) {
	testlapack.Dgetf2Test(t, impl)
}

func TestDgetrf(t *testing.T) {
	testlapack.DgetrfTest(t, impl)
}

func TestDgetri(t *testing.T) {
	testlapack.DgetriTest(t, impl)
}

func TestDgetrs(t *testing.T) {
	testlapack.DgetrsTest(t, impl)
}

func TestDhseqr(t *testing.T) {
	testlapack.DhseqrTest(t, impl)
}

func TestDorglq(t *testing.T) {
	testlapack.DorglqTest(t, blockedTranslate{impl})
}

func TestDorgqr(t *testing.T) {
	testlapack.DorgqrTest(t, blockedTranslate{impl})
}

func TestDorgl2(t *testing.T) {
	testlapack.Dorgl2Test(t, blockedTranslate{impl})
}

func TestDorg2r(t *testing.T) {
	testlapack.Dorg2rTest(t, blockedTranslate{impl})
}

/*
// Test disabled because of bug in c interface. Leaving stub for easy reproducer.
//
// Bug at: https://github.com/xianyi/OpenBLAS/issues/712
// Fix at: https://github.com/xianyi/OpenBLAS/pull/713
// Easily copiable fix: https://github.com/gonum/lapack/pull/74#issuecomment-163142140
func TestDormbr(t *testing.T) {
	testlapack.DormbrTest(t, blockedTranslate{impl})
}
*/

func TestDormhr(t *testing.T) {
	testlapack.DormhrTest(t, impl)
}

func TestDorgbr(t *testing.T) {
	testlapack.DorgbrTest(t, blockedTranslate{impl})
}

func TestDorghr(t *testing.T) {
	testlapack.DorghrTest(t, impl)
}

func TestDormqr(t *testing.T) {
	testlapack.Dorm2rTest(t, blockedTranslate{impl})
}

/*
// Test disabled because of bug in c interface. Leaving stub for easy reproducer.
//
// Bug at: https://github.com/xianyi/OpenBLAS/issues/615
// Fix at: https://github.com/xianyi/OpenBLAS/pull/711
// Easily copiable fix: https://github.com/gonum/lapack/pull/74#issuecomment-163110751
func TestDormlq(t *testing.T) {
	testlapack.Dorml2Test(t, blockedTranslate{impl})
}
*/

func TestDpocon(t *testing.T) {
	testlapack.DpoconTest(t, impl)
}

func TestDsyev(t *testing.T) {
	testlapack.DsyevTest(t, impl)
}

func TestDtrexc(t *testing.T) {
	testlapack.DtrexcTest(t, impl)
}

func TestDtrcon(t *testing.T) {
	testlapack.DtrconTest(t, impl)
}

func TestDtrtri(t *testing.T) {
	testlapack.DtrtriTest(t, impl)
}
<commit_msg>cgo: enable test for Dorgql<commit_after>// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgo

import (
	"testing"

	"github.com/gonum/blas"
	"github.com/gonum/lapack/testlapack"
)

var impl = Implementation{}

// blockedTranslate transforms some blocked C calls to be the unblocked algorithms
// for testing, as several of the unblocked algorithms are not defined by the C
// interface.
type blockedTranslate struct {
	Implementation
}

func TestDbdsqr(t *testing.T) {
	testlapack.DbdsqrTest(t, impl)
}

func (bl blockedTranslate) Dgebd2(m, n int, a []float64, lda int, d, e, tauQ, tauP, work []float64) {
	impl.Dgebrd(m, n, a, lda, d, e, tauQ, tauP, work, len(work))
}

func (bl blockedTranslate) Dorm2r(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64) {
	impl.Dormqr(side, trans, m, n, k, a, lda, tau, c, ldc, work, len(work))
}

func (bl blockedTranslate) Dorml2(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64) {
	impl.Dormlq(side, trans, m, n, k, a, lda, tau, c, ldc, work, len(work))
}

func (bl blockedTranslate) Dorg2r(m, n, k int, a []float64, lda int, tau, work []float64) {
	impl.Dorgqr(m, n, k, a, lda, tau, work, len(work))
}

func (bl blockedTranslate) Dorgl2(m, n, k int, a []float64, lda int, tau, work []float64) {
	impl.Dorglq(m, n, k, a, lda, tau, work, len(work))
}

func TestDlacn2(t *testing.T) {
	testlapack.Dlacn2Test(t, impl)
}

func TestDlacpy(t *testing.T) {
	testlapack.DlacpyTest(t, impl)
}

func TestDlange(t *testing.T) {
	testlapack.DlangeTest(t, impl)
}

func TestDlarfb(t *testing.T) {
	testlapack.DlarfbTest(t, impl)
}

func TestDlarfg(t *testing.T) {
	testlapack.DlarfgTest(t, impl)
}

func TestDlarft(t *testing.T) {
	testlapack.DlarftTest(t, impl)
}

func TestDlantr(t *testing.T) {
	testlapack.DlantrTest(t, impl)
}

func TestDlarfx(t *testing.T) {
	testlapack.DlarfxTest(t, impl)
}

func TestDpotrf(t *testing.T) {
	testlapack.DpotrfTest(t, impl)
}

func TestDgebak(t *testing.T) {
	testlapack.DgebakTest(t, impl)
}

func TestDgebal(t *testing.T) {
	testlapack.DgebalTest(t, impl)
}

func TestDgebd2(t *testing.T) {
	testlapack.Dgebd2Test(t, blockedTranslate{impl})
}

func TestDgecon(t *testing.T) {
	testlapack.DgeconTest(t, impl)
}

func TestDgeev(t *testing.T) {
	testlapack.DgeevTest(t, impl)
}

func TestDgehrd(t *testing.T) {
	testlapack.DgehrdTest(t, impl)
}

func TestDgelq2(t *testing.T) {
	testlapack.Dgelq2Test(t, impl)
}

func TestDgels(t *testing.T) {
	testlapack.DgelsTest(t, impl)
}

func TestDgelqf(t *testing.T) {
	testlapack.DgelqfTest(t, impl)
}

func TestDgeqr2(t *testing.T) {
	testlapack.Dgeqr2Test(t, impl)
}

func TestDgeqrf(t *testing.T) {
	testlapack.DgeqrfTest(t, impl)
}

func TestDgesvd(t *testing.T) {
	testlapack.DgesvdTest(t, impl)
}

func TestDgetf2(t *testing.T) {
	testlapack.Dgetf2Test(t, impl)
}

func TestDgetrf(t *testing.T) {
	testlapack.DgetrfTest(t, impl)
}

func TestDgetri(t *testing.T) {
	testlapack.DgetriTest(t, impl)
}

func TestDgetrs(t *testing.T) {
	testlapack.DgetrsTest(t, impl)
}

func TestDhseqr(t *testing.T) {
	testlapack.DhseqrTest(t, impl)
}

func TestDorglq(t *testing.T) {
	testlapack.DorglqTest(t, blockedTranslate{impl})
}

func TestDorgql(t *testing.T) {
	testlapack.DorgqlTest(t, impl)
}

func TestDorgqr(t *testing.T) {
	testlapack.DorgqrTest(t, blockedTranslate{impl})
}

func TestDorgl2(t *testing.T) {
	testlapack.Dorgl2Test(t, blockedTranslate{impl})
}

func TestDorg2r(t *testing.T) {
	testlapack.Dorg2rTest(t, blockedTranslate{impl})
}

/*
// Test disabled because of bug in c interface. Leaving stub for easy reproducer.
//
// Bug at: https://github.com/xianyi/OpenBLAS/issues/712
// Fix at: https://github.com/xianyi/OpenBLAS/pull/713
// Easily copiable fix: https://github.com/gonum/lapack/pull/74#issuecomment-163142140
func TestDormbr(t *testing.T) {
	testlapack.DormbrTest(t, blockedTranslate{impl})
}
*/

func TestDormhr(t *testing.T) {
	testlapack.DormhrTest(t, impl)
}

func TestDorgbr(t *testing.T) {
	testlapack.DorgbrTest(t, blockedTranslate{impl})
}

func TestDorghr(t *testing.T) {
	testlapack.DorghrTest(t, impl)
}

func TestDormqr(t *testing.T) {
	testlapack.Dorm2rTest(t, blockedTranslate{impl})
}

/*
// Test disabled because of bug in c interface. Leaving stub for easy reproducer.
//
// Bug at: https://github.com/xianyi/OpenBLAS/issues/615
// Fix at: https://github.com/xianyi/OpenBLAS/pull/711
// Easily copiable fix: https://github.com/gonum/lapack/pull/74#issuecomment-163110751
func TestDormlq(t *testing.T) {
	testlapack.Dorml2Test(t, blockedTranslate{impl})
}
*/

func TestDpocon(t *testing.T) {
	testlapack.DpoconTest(t, impl)
}

func TestDsyev(t *testing.T) {
	testlapack.DsyevTest(t, impl)
}

func TestDtrexc(t *testing.T) {
	testlapack.DtrexcTest(t, impl)
}

func TestDtrcon(t *testing.T) {
	testlapack.DtrconTest(t, impl)
}

func TestDtrtri(t *testing.T) {
	testlapack.DtrtriTest(t, impl)
}
<|endoftext|>


<commit_before>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ldd

import (
	"fmt"
	"path/filepath"
)

const ldso = "/lib*/ld-linux-*.so.*"

func LdSo() (string, error) {
	n, err := filepath.Glob(ldso)
	if err != nil {
		return "", err
	}
	if len(n) == 0 {
		return "", fmt.Errorf("No ld.so matches %v", ldso)
	}
	return n[0], nil
}
<commit_msg>fix ldd on some musl-based Linux distros<commit_after>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ldd

import (
	"fmt"
	"path/filepath"
)

const ldso = "/lib*/ld-*.so.*"

func LdSo() (string, error) {
	n, err := filepath.Glob(ldso)
	if err != nil {
		return "", err
	}
	if len(n) == 0 {
		return "", fmt.Errorf("No ld.so matches %v", ldso)
	}
	return n[0], nil
}
<|endoftext|>


<commit_before>package metrics

import (
	"fmt"
	"math"
	"net/http"
	"sync"
	"time"

	"github.com/glendc/data-ingestion-challenge/pkg/log"
)

// DefaultServerConfig creates a server config with sane defaults
func DefaultServerConfig() *ServerConfig {
	return &ServerConfig{
		ResponseBufferSize: 256,
		RequestBufferSize:  1024,
	}
}

// ServerConfig is used to configure a server metrics worker
type ServerConfig struct {
	ResponseBufferSize int
	RequestBufferSize  int
}

// WithResponseBufferSize sets the response buffer size configuration
// and returns the updated version of itself
func (cfg *ServerConfig) WithResponseBufferSize(size int) *ServerConfig {
	cfg.ResponseBufferSize = size
	return cfg
}

// WithRequestBufferSize sets the request buffer size configuration
// and returns the updated version of itself
func (cfg *ServerConfig) WithRequestBufferSize(size int) *ServerConfig {
	cfg.RequestBufferSize = size
	return cfg
}

// validate the ServerConfig properties
func (cfg *ServerConfig) validate() error {
	if cfg.ResponseBufferSize < 8 {
		return fmt.Errorf(
			"%d is an invalid ResponseBufferSize, should be at least 8",
			cfg.ResponseBufferSize)
	}
	if cfg.RequestBufferSize < 1 {
		return fmt.Errorf(
			"%d is an invalid RequestBufferSize, should be at least 1",
			cfg.RequestBufferSize)
	}
	return nil
}

// Server collect all metrics we want to track about a server
type Server struct {
	requests       uint64
	failedRequests uint64

	minRespTime         time.Duration
	maxRespTime         time.Duration
	respTimeBuffer      []time.Duration
	respTimeBufferIndex int
	respStartTime       time.Time

	mtx sync.Mutex
	// constants given by user

	ch chan serverInput
	// constants given by user
	cfg *ServerConfig
}

// NewServer creates a metrics worker that is meant
// to track and compute the metrics of an active server
func NewServer(cfg *ServerConfig) (*Server, error) {
	if cfg == nil {
		cfg = DefaultServerConfig()
	} else if err := cfg.validate(); err != nil {
		return nil, fmt.Errorf("can't create server as config is invalid: %q", err)
	}

	return &Server{
		minRespTime:    time.Duration(math.MaxInt64),
		maxRespTime:    time.Duration(math.MinInt64),
		respTimeBuffer: make([]time.Duration, cfg.ResponseBufferSize),
		ch:             make(chan serverInput, cfg.RequestBufferSize),
		cfg:            cfg,
	}, nil
}

// ListenAndCompute listens for incoming track requests,
// it computes all requests in the order they come in.
func (s *Server) ListenAndCompute() {
	log.Infof("Server Metrics Worker up and running! Waiting for requests to come in...")
	for {
		select {
		case input := <-s.ch:
			s.trackRequest(&input)
		}
	}
}

// Request notifies the Server Metrics worker
// about a new request, which info it will track ASAP
func (s *Server) Request(r *http.Request, start time.Time, success bool) {
	s.ch <- serverInput{
		RespTime: time.Now().Sub(start),
		Success:  success,
	}
}

// String returns the server metrics as a valid JSON Object,
// implementing the expvar.Var interface
func (s *Server) String() string {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	// early return in case no requests have been progressed yet,
	// as all the other data is pretty useless if so
	if s.requests == 0 {
		return `{
			"requests": {
				"total": 0
			}
		}`
	}

	successRequests := s.requests - s.failedRequests
	avgRespTime := s.computeAverageRespTime()

	return fmt.Sprintf(`{
		"requests": {
			"total": %d,
			"failed": %d,
			"successfull": %d,
		},
		"responseTimes": {
			"minimum": %f,
			"maximum": %f,
			"average": %f,
		},
	}`,
		s.requests,
		s.failedRequests,
		successRequests,
		s.minRespTime.Seconds(),
		s.maxRespTime.Seconds(),
		avgRespTime.Seconds(),
	)
}

// server metric input
type serverInput struct {
	RespTime time.Duration
	Success  bool
}

// track a request, for most data we only care about successfull requests
func (s *Server) trackRequest(in *serverInput) {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	s.requests++

	// if the request was not successfull we don't compute response times
	// as this would skew the data, knowing that failed requests
	// are much quicker to process in many cases
	// (only when RabbitMQ fails this wouldn't be the case)
	if !in.Success {
		s.failedRequests++
		return
	}

	// compute min/max resp time values
	if in.RespTime < s.minRespTime {
		s.minRespTime = in.RespTime
	}
	if in.RespTime > s.maxRespTime {
		s.maxRespTime = in.RespTime
	}

	// store resp time, as this can be used to compute the average
	index := s.respTimeBufferIndex % s.cfg.ResponseBufferSize
	s.respTimeBuffer[index] = in.RespTime
	s.respTimeBufferIndex++
}

// computeAverageRespTime computes the average response time
// it does so by taking into account all last N response times
// where N is the number equal to the size of the buffer, capped at a maximum.
func (s *Server) computeAverageRespTime() time.Duration {
	var avgRespTime time.Duration
	respSize := s.cfg.ResponseBufferSize
	if uint64(respSize) > s.requests {
		respSize = int(s.requests)
	}

	respIndex := s.respTimeBufferIndex - respSize
	if respIndex < 0 {
		respIndex = respSize + respIndex
	}

	for i := 0; i < respSize; i++ {
		avgRespTime += s.respTimeBuffer[respIndex%s.cfg.ResponseBufferSize]
		respIndex++
	}

	return avgRespTime / time.Duration(respSize)
}
<commit_msg>bugfix server metrics avg computation<commit_after>package metrics

import (
	"fmt"
	"math"
	"net/http"
	"sync"
	"time"

	"github.com/glendc/data-ingestion-challenge/pkg/log"
)

// DefaultServerConfig creates a server config with sane defaults
func DefaultServerConfig() *ServerConfig {
	return &ServerConfig{
		ResponseBufferSize: 256,
		RequestBufferSize:  1024,
	}
}

// ServerConfig is used to configure a server metrics worker
type ServerConfig struct {
	ResponseBufferSize int
	RequestBufferSize  int
}

// WithResponseBufferSize sets the response buffer size configuration
// and returns the updated version of itself
func (cfg *ServerConfig) WithResponseBufferSize(size int) *ServerConfig {
	cfg.ResponseBufferSize = size
	return cfg
}

// WithRequestBufferSize sets the request buffer size configuration
// and returns the updated version of itself
func (cfg *ServerConfig) WithRequestBufferSize(size int) *ServerConfig {
	cfg.RequestBufferSize = size
	return cfg
}

// validate the ServerConfig properties
func (cfg *ServerConfig) validate() error {
	if cfg.ResponseBufferSize < 8 {
		return fmt.Errorf(
			"%d is an invalid ResponseBufferSize, should be at least 8",
			cfg.ResponseBufferSize)
	}
	if cfg.RequestBufferSize < 1 {
		return fmt.Errorf(
			"%d is an invalid RequestBufferSize, should be at least 1",
			cfg.RequestBufferSize)
	}
	return nil
}

// Server collect all metrics we want to track about a server
type Server struct {
	requests       uint64
	failedRequests uint64

	minRespTime         time.Duration
	maxRespTime         time.Duration
	respTimeBuffer      []time.Duration
	respTimeBufferIndex int
	respStartTime       time.Time

	mtx sync.Mutex
	// constants given by user

	ch chan serverInput
	// constants given by user
	cfg *ServerConfig
}

// NewServer creates a metrics worker that is meant
// to track and compute the metrics of an active server
func NewServer(cfg *ServerConfig) (*Server, error) {
	if cfg == nil {
		cfg = DefaultServerConfig()
	} else if err := cfg.validate(); err != nil {
		return nil, fmt.Errorf("can't create server as config is invalid: %q", err)
	}

	return &Server{
		minRespTime:    time.Duration(math.MaxInt64),
		maxRespTime:    time.Duration(math.MinInt64),
		respTimeBuffer: make([]time.Duration, cfg.ResponseBufferSize),
		ch:             make(chan serverInput, cfg.RequestBufferSize),
		cfg:            cfg,
	}, nil
}

// ListenAndCompute listens for incoming track requests,
// it computes all requests in the order they come in.
func (s *Server) ListenAndCompute() {
	log.Infof("Server Metrics Worker up and running! Waiting for requests to come in...")
	for {
		select {
		case input := <-s.ch:
			s.trackRequest(&input)
		}
	}
}

// Request notifies the Server Metrics worker
// about a new request, which info it will track ASAP
func (s *Server) Request(r *http.Request, start time.Time, success bool) {
	s.ch <- serverInput{
		RespTime: time.Now().Sub(start),
		Success:  success,
	}
}

// String returns the server metrics as a valid JSON Object,
// implementing the expvar.Var interface
func (s *Server) String() string {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	// early return in case no requests have been progressed yet,
	// as all the other data is pretty useless if so
	if s.requests == 0 {
		return `{
			"requests": {
				"total": 0
			}
		}`
	}

	successRequests := s.requests - s.failedRequests
	avgRespTime := s.computeAverageRespTime(successRequests)

	return fmt.Sprintf(`{
		"requests": {
			"total": %d,
			"failed": %d,
			"successfull": %d,
		},
		"responseTimes": {
			"minimum": %f,
			"maximum": %f,
			"average": %f,
		},
	}`,
		s.requests,
		s.failedRequests,
		successRequests,
		s.minRespTime.Seconds(),
		s.maxRespTime.Seconds(),
		avgRespTime.Seconds(),
	)
}

// server metric input
type serverInput struct {
	RespTime time.Duration
	Success  bool
}

// track a request, for most data we only care about successfull requests
func (s *Server) trackRequest(in *serverInput) {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	s.requests++

	// if the request was not successfull we don't compute response times
	// as this would skew the data, knowing that failed requests
	// are much quicker to process in many cases
	// (only when RabbitMQ fails this wouldn't be the case)
	if !in.Success {
		s.failedRequests++
		return
	}

	// compute min/max resp time values
	if in.RespTime < s.minRespTime {
		s.minRespTime = in.RespTime
	}
	if in.RespTime > s.maxRespTime {
		s.maxRespTime = in.RespTime
	}

	// store resp time, as this can be used to compute the average
	index := s.respTimeBufferIndex % s.cfg.ResponseBufferSize
	s.respTimeBuffer[index] = in.RespTime
	s.respTimeBufferIndex++
}

// computeAverageRespTime computes the average response time
// it does so by taking into account all last N response times
// where N is the number equal to the size of the buffer, capped at a maximum.
func (s *Server) computeAverageRespTime(sucRequests uint64) time.Duration {
	var avgRespTime time.Duration
	respSize := s.cfg.ResponseBufferSize
	if uint64(respSize) > sucRequests {
		respSize = int(sucRequests)
	}

	respIndex := s.respTimeBufferIndex - respSize
	if respIndex < 0 {
		respIndex = respSize + respIndex
	}

	for i := 0; i < respSize; i++ {
		avgRespTime += s.respTimeBuffer[respIndex%s.cfg.ResponseBufferSize]
		respIndex++
	}

	return avgRespTime / time.Duration(respSize)
}
<|endoftext|>


<commit_before>package paths

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"testing"

	. "github.com/VonC/godbg"
	"github.com/VonC/senvgo/prgs"
	. "github.com/smartystreets/goconvey/convey"
)

func TestPath(t *testing.T) {

	FocusConvey("Tests for Path", t, func() {
		Convey("An empty path remains empty", func() {
			SetBuffers(nil)
			p := NewPath("")
			So(p.path, ShouldEqual, "")
			So(NoOutput(), ShouldBeTrue)
		})

		Convey("An http path remains unchanged", func() {
			SetBuffers(nil)
			p := NewPath(`http://a\b/../c`)
			So(p.path, ShouldEqual, `http://a\b/../c`)
			So(NoOutput(), ShouldBeTrue)
		})
		Convey("A path without trailing / must have one if it is an existing folder", func() {
			SetBuffers(nil)
			p := NewPath(`../paths`)
			So(p.path, ShouldEqual, `..\paths\`)
			So(NoOutput(), ShouldBeTrue)
		})
		Convey("A path without trailing / must keep it even if it is not an existing folder", func() {
			SetBuffers(nil)
			p := NewPath(`xxx\`)
			p = NewPath(`xxx/`)
			So(p.path, ShouldEqual, `xxx\`)
			So(NoOutput(), ShouldBeTrue)
		})

		FocusConvey("A Path can test if it is a Dir", func() {
			SetBuffers(nil)
			p := NewPath("")
			So(p.IsDir(), ShouldBeFalse)
			So(OutString(), ShouldBeEmpty)
			So(ErrString(), ShouldEqual, `open : The system cannot find the file specified.
`)

			// Existing files
			SetBuffers(nil)
			p = NewPath("paths_test.go")
			So(p.IsDir(), ShouldBeFalse)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `paths_test.go`)

			// Existing folders
			SetBuffers(nil)
			p = NewPath("..")
			So(p.IsDir(), ShouldBeTrue)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `..\`)
			p = NewPath("../paths")
			So(p.IsDir(), ShouldBeTrue)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `..\paths\`)

			// Errors
			SetBuffers(nil)
			fstat = testerrfstat
			p = NewPath("..")
			So(p.IsDir(), ShouldBeFalse)
			So(OutString(), ShouldBeEmpty)
			So(ErrString(), ShouldEqual, `fstat error on '..'
fstat error on '..'
`)
			So(p.path, ShouldEqual, `..`)
		})
	})
}

func testerrfstat(f *os.File) (fi os.FileInfo, err error) {
	return nil, fmt.Errorf("fstat error on '%+v'", f.Name())
}

type testPrg struct{ name string }

func (tp *testPrg) Name() string { return tp.name }

type testPathWriter struct{ b *bytes.Buffer }

type testWriter struct{ w io.Writer }

func (tw *testWriter) Write(p []byte) (n int, err error) {
	s := string(p)
	if s == "prg2" {
		return 0, fmt.Errorf("Error writing '%s'", s)
	}
	return tw.w.Write(p)
}

func (tpw *testPathWriter) WritePath(prgs []prgs.Prg, w io.Writer) error {
	if err := pw.WritePath(prgs, w); err != nil {
		return err
	}
	return nil
}

func TestMain(t *testing.T) {
	tpw := &testPathWriter{b: bytes.NewBuffer(nil)}
	prgs := []prgs.Prg{&testPrg{name: "prg1"}, &testPrg{name: "prg2"}}
	Convey("Tests for Path Writer", t, func() {

		Convey("A Path writer writes any empty path if no prgs", func() {
			SetBuffers(nil)
			err := tpw.WritePath(prgs, tpw.b)
			So(err, ShouldBeNil)
			So(tpw.b.String(), ShouldEqual, "prg1prg2")
			So(NoOutput(), ShouldBeTrue)
		})

		Convey("A Path writer can report error during writing", func() {
			SetBuffers(nil)
			tpw.b = bytes.NewBuffer(nil)
			tw := &testWriter{w: tpw.b}
			err := tpw.WritePath(prgs, tw)
			So(err.Error(), ShouldEqual, "Error writing 'prg2'")
			So(tpw.b.String(), ShouldEqual, "prg1")
			So(NoOutput(), ShouldBeTrue)
		})
	})
}
<commit_msg>IsDir() test: remove Focus<commit_after>package paths

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"testing"

	. "github.com/VonC/godbg"
	"github.com/VonC/senvgo/prgs"
	. "github.com/smartystreets/goconvey/convey"
)

func TestPath(t *testing.T) {

	FocusConvey("Tests for Path", t, func() {
		Convey("An empty path remains empty", func() {
			SetBuffers(nil)
			p := NewPath("")
			So(p.path, ShouldEqual, "")
			So(NoOutput(), ShouldBeTrue)
		})

		Convey("An http path remains unchanged", func() {
			SetBuffers(nil)
			p := NewPath(`http://a\b/../c`)
			So(p.path, ShouldEqual, `http://a\b/../c`)
			So(NoOutput(), ShouldBeTrue)
		})
		Convey("A path without trailing / must have one if it is an existing folder", func() {
			SetBuffers(nil)
			p := NewPath(`../paths`)
			So(p.path, ShouldEqual, `..\paths\`)
			So(NoOutput(), ShouldBeTrue)
		})
		Convey("A path without trailing / must keep it even if it is not an existing folder", func() {
			SetBuffers(nil)
			p := NewPath(`xxx\`)
			p = NewPath(`xxx/`)
			So(p.path, ShouldEqual, `xxx\`)
			So(NoOutput(), ShouldBeTrue)
		})

		Convey("A Path can test if it is a Dir", func() {
			SetBuffers(nil)
			p := NewPath("")
			So(p.IsDir(), ShouldBeFalse)
			So(OutString(), ShouldBeEmpty)
			So(ErrString(), ShouldEqual, `open : The system cannot find the file specified.
`)

			// Existing files
			SetBuffers(nil)
			p = NewPath("paths_test.go")
			So(p.IsDir(), ShouldBeFalse)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `paths_test.go`)

			// Existing folders
			SetBuffers(nil)
			p = NewPath("..")
			So(p.IsDir(), ShouldBeTrue)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `..\`)
			p = NewPath("../paths")
			So(p.IsDir(), ShouldBeTrue)
			So(NoOutput(), ShouldBeTrue)
			So(p.path, ShouldEqual, `..\paths\`)

			// Errors
			SetBuffers(nil)
			fstat = testerrfstat
			p = NewPath("..")
			So(p.IsDir(), ShouldBeFalse)
			So(OutString(), ShouldBeEmpty)
			So(ErrString(), ShouldEqual, `fstat error on '..'
fstat error on '..'
`)
			So(p.path, ShouldEqual, `..`)
			fstat = ifstat

		})
	})
}

func testerrfstat(f *os.File) (fi os.FileInfo, err error) {
	return nil, fmt.Errorf("fstat error on '%+v'", f.Name())
}

type testPrg struct{ name string }

func (tp *testPrg) Name() string { return tp.name }

type testPathWriter struct{ b *bytes.Buffer }

type testWriter struct{ w io.Writer }

func (tw *testWriter) Write(p []byte) (n int, err error) {
	s := string(p)
	if s == "prg2" {
		return 0, fmt.Errorf("Error writing '%s'", s)
	}
	return tw.w.Write(p)
}

func (tpw *testPathWriter) WritePath(prgs []prgs.Prg, w io.Writer) error {
	if err := pw.WritePath(prgs, w); err != nil {
		return err
	}
	return nil
}

func TestMain(t *testing.T) {
	tpw := &testPathWriter{b: bytes.NewBuffer(nil)}
	prgs := []prgs.Prg{&testPrg{name: "prg1"}, &testPrg{name: "prg2"}}
	Convey("Tests for Path Writer", t, func() {

		Convey("A Path writer writes any empty path if no prgs", func() {
			SetBuffers(nil)
			err := tpw.WritePath(prgs, tpw.b)
			So(err, ShouldBeNil)
			So(tpw.b.String(), ShouldEqual, "prg1prg2")
			So(NoOutput(), ShouldBeTrue)
		})

		Convey("A Path writer can report error during writing", func() {
			SetBuffers(nil)
			tpw.b = bytes.NewBuffer(nil)
			tw := &testWriter{w: tpw.b}
			err := tpw.WritePath(prgs, tw)
			So(err.Error(), ShouldEqual, "Error writing 'prg2'")
			So(tpw.b.String(), ShouldEqual, "prg1")
			So(NoOutput(), ShouldBeTrue)
		})
	})
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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
//
//     https://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 operator

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v3"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"

	"github.com/GoogleCloudPlatform/prometheus-engine/pkg/export"
	monitoringv1alpha1 "github.com/GoogleCloudPlatform/prometheus-engine/pkg/operator/apis/monitoring/v1alpha1"
	"github.com/GoogleCloudPlatform/prometheus-engine/pkg/rules"
)

const (
	nameRulesGenerated = "rules-generated"
)

func setupRulesControllers(op *Operator) error {
	// The singleton OperatorConfig is the request object we reconcile against.
	objRequest := reconcile.Request{
		NamespacedName: types.NamespacedName{
			Namespace: op.opts.PublicNamespace,
			Name:      NameOperatorConfig,
		},
	}
	// Default OperatorConfig filter.
	objFilterOperatorConfig := namespacedNamePredicate{
		namespace: op.opts.PublicNamespace,
		name:      NameOperatorConfig,
	}
	// Rule-evaluator rules ConfigMap filter.
	objFilterRulesGenerated := namespacedNamePredicate{
		namespace: op.opts.OperatorNamespace,
		name:      nameRulesGenerated,
	}

	// Reconcile the generated rules that are used by the rule-evaluator deployment.
	err := ctrl.NewControllerManagedBy(op.manager).
		Named("rules").
		// Filter events without changes for all watches.
		WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
		// OperatorConfig is our root resource that ensures we reconcile
		// at least once initially.
		For(
			&monitoringv1alpha1.OperatorConfig{},
			builder.WithPredicates(objFilterOperatorConfig),
		).
		// Any update to a Rules object requires re-generating the config.
		Watches(
			&source.Kind{Type: &monitoringv1alpha1.Rules{}},
			enqueueConst(objRequest),
		).
		Watches(
			&source.Kind{Type: &monitoringv1alpha1.ClusterRules{}},
			enqueueConst(objRequest),
		).
		// The configuration we generate for the rule-evaluator.
		Watches(
			&source.Kind{Type: &corev1.ConfigMap{}},
			enqueueConst(objRequest),
			builder.WithPredicates(objFilterRulesGenerated),
		).
		Complete(newRulesReconciler(op.manager.GetClient(), op.opts))
	if err != nil {
		return errors.Wrap(err, "create rules config controller")
	}
	return nil
}

type rulesReconciler struct {
	client client.Client
	opts   Options
}

func newRulesReconciler(c client.Client, opts Options) *rulesReconciler {
	return &rulesReconciler{
		client: c,
		opts:   opts,
	}
}

func (r *rulesReconciler) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
	logr.FromContext(ctx).Info("reconciling rules")

	if err := r.ensureRuleConfigs(ctx); err != nil {
		return reconcile.Result{}, errors.Wrap(err, "ensure rule configmaps")
	}
	return reconcile.Result{}, nil
}

func (r *rulesReconciler) ensureRuleConfigs(ctx context.Context) error {
	logger := logr.FromContext(ctx)

	// Re-generate the configmap that's loaded by the rule-evaluator.
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.opts.OperatorNamespace,
			Name:      nameRulesGenerated,
			Labels: map[string]string{
				LabelAppName: NameRuleEvaluator,
			},
		},
		// Ensure there's always at least an empty dummy file as the evaluator
		// expects at least one match.
		Data: map[string]string{
			"empty.yaml": "",
		},
	}

	// Generate a final rule file for each Rules resource.
	//
	// Depending on the scope level (global, cluster, namespace) the rules will be generated
	// so that queries are constrained to the appropriate project_id, cluster, and namespace
	// labels and that they are preserved through query aggregations and appear on the
	// output data.
	//
	// The location is not scoped as it's not a meaningful boundary for "human access"
	// to data as clusters may span locations.
	var rulesList monitoringv1alpha1.RulesList
	if err := r.client.List(ctx, &rulesList); err != nil {
		return errors.Wrap(err, "list rules")
	}
	for _, rs := range rulesList.Items {
		result, err := generateRules(&rs, r.opts)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "rules_namespace", rs.Namespace, "rules_name", rs.Name)
		}
		filename := fmt.Sprintf("rules__%s__%s.yaml", rs.Namespace, rs.Name)
		cm.Data[filename] = result
	}

	var clusterRulesList monitoringv1alpha1.ClusterRulesList
	if err := r.client.List(ctx, &clusterRulesList); err != nil {
		return errors.Wrap(err, "list cluster rules")
	}
	for _, rs := range clusterRulesList.Items {
		result, err := generateClusterRules(&rs, r.opts)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "clusterrules_name", rs.Name)
		}
		filename := fmt.Sprintf("clusterrules__%s.yaml", rs.Name)
		cm.Data[filename] = string(result)
	}

	var globalRulesList monitoringv1alpha1.GlobalRulesList
	if err := r.client.List(ctx, &globalRulesList); err != nil {
		return errors.Wrap(err, "list global rules")
	}
	for _, rs := range globalRulesList.Items {
		result, err := generateGlobalRules(&rs)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "globalrules_name", rs.Name)
		}
		filename := fmt.Sprintf("globalrules__%s.yaml", rs.Name)
		cm.Data[filename] = string(result)
	}

	// Create or update generated rule ConfigMap.
	if err := r.client.Update(ctx, cm); apierrors.IsNotFound(err) {
		if err := r.client.Create(ctx, cm); err != nil {
			return errors.Wrap(err, "create generated rules")
		}
	} else if err != nil {
		return errors.Wrap(err, "update generated rules")
	}
	return nil
}

func generateRules(apiRules *monitoringv1alpha1.Rules, opts Options) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	if err := rules.Scope(&rs, map[string]string{
		export.KeyProjectID: opts.ProjectID,
		export.KeyCluster:   opts.Cluster,
		export.KeyLocation:  opts.Location,
		export.KeyNamespace: apiRules.Namespace,
	}); err != nil {
		return "", errors.Wrap(err, "isolating rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

func generateClusterRules(apiRules *monitoringv1alpha1.ClusterRules, opts Options) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	if err := rules.Scope(&rs, map[string]string{
		export.KeyProjectID: opts.ProjectID,
		export.KeyLocation:  opts.Location,
		export.KeyCluster:   opts.Cluster,
	}); err != nil {
		return "", errors.Wrap(err, "isolating rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

func generateGlobalRules(apiRules *monitoringv1alpha1.GlobalRules) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

type rulesValidator struct {
	opts Options
}

func (v *rulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateRules(o.(*monitoringv1alpha1.Rules), v.opts)
	return err
}

func (v *rulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *rulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}

type clusterRulesValidator struct {
	opts Options
}

func (v *clusterRulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateClusterRules(o.(*monitoringv1alpha1.ClusterRules), v.opts)
	return err
}

func (v *clusterRulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *clusterRulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}

type globalRulesValidator struct{}

func (v *globalRulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateGlobalRules(o.(*monitoringv1alpha1.GlobalRules))
	return err
}

func (v *globalRulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *globalRulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}
<commit_msg>Watch global rules<commit_after>// Copyright 2021 Google LLC
//
// 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
//
//     https://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 operator

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v3"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"

	"github.com/GoogleCloudPlatform/prometheus-engine/pkg/export"
	monitoringv1alpha1 "github.com/GoogleCloudPlatform/prometheus-engine/pkg/operator/apis/monitoring/v1alpha1"
	"github.com/GoogleCloudPlatform/prometheus-engine/pkg/rules"
)

const (
	nameRulesGenerated = "rules-generated"
)

func setupRulesControllers(op *Operator) error {
	// The singleton OperatorConfig is the request object we reconcile against.
	objRequest := reconcile.Request{
		NamespacedName: types.NamespacedName{
			Namespace: op.opts.PublicNamespace,
			Name:      NameOperatorConfig,
		},
	}
	// Default OperatorConfig filter.
	objFilterOperatorConfig := namespacedNamePredicate{
		namespace: op.opts.PublicNamespace,
		name:      NameOperatorConfig,
	}
	// Rule-evaluator rules ConfigMap filter.
	objFilterRulesGenerated := namespacedNamePredicate{
		namespace: op.opts.OperatorNamespace,
		name:      nameRulesGenerated,
	}

	// Reconcile the generated rules that are used by the rule-evaluator deployment.
	err := ctrl.NewControllerManagedBy(op.manager).
		Named("rules").
		// Filter events without changes for all watches.
		WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
		// OperatorConfig is our root resource that ensures we reconcile
		// at least once initially.
		For(
			&monitoringv1alpha1.OperatorConfig{},
			builder.WithPredicates(objFilterOperatorConfig),
		).
		// Any update to a Rules object requires re-generating the config.
		Watches(
			&source.Kind{Type: &monitoringv1alpha1.GlobalRules{}},
			enqueueConst(objRequest),
		).
		Watches(
			&source.Kind{Type: &monitoringv1alpha1.ClusterRules{}},
			enqueueConst(objRequest),
		).
		Watches(
			&source.Kind{Type: &monitoringv1alpha1.Rules{}},
			enqueueConst(objRequest),
		).
		// The configuration we generate for the rule-evaluator.
		Watches(
			&source.Kind{Type: &corev1.ConfigMap{}},
			enqueueConst(objRequest),
			builder.WithPredicates(objFilterRulesGenerated),
		).
		Complete(newRulesReconciler(op.manager.GetClient(), op.opts))
	if err != nil {
		return errors.Wrap(err, "create rules config controller")
	}
	return nil
}

type rulesReconciler struct {
	client client.Client
	opts   Options
}

func newRulesReconciler(c client.Client, opts Options) *rulesReconciler {
	return &rulesReconciler{
		client: c,
		opts:   opts,
	}
}

func (r *rulesReconciler) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
	logr.FromContext(ctx).Info("reconciling rules")

	if err := r.ensureRuleConfigs(ctx); err != nil {
		return reconcile.Result{}, errors.Wrap(err, "ensure rule configmaps")
	}
	return reconcile.Result{}, nil
}

func (r *rulesReconciler) ensureRuleConfigs(ctx context.Context) error {
	logger := logr.FromContext(ctx)

	// Re-generate the configmap that's loaded by the rule-evaluator.
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.opts.OperatorNamespace,
			Name:      nameRulesGenerated,
			Labels: map[string]string{
				LabelAppName: NameRuleEvaluator,
			},
		},
		// Ensure there's always at least an empty dummy file as the evaluator
		// expects at least one match.
		Data: map[string]string{
			"empty.yaml": "",
		},
	}

	// Generate a final rule file for each Rules resource.
	//
	// Depending on the scope level (global, cluster, namespace) the rules will be generated
	// so that queries are constrained to the appropriate project_id, cluster, and namespace
	// labels and that they are preserved through query aggregations and appear on the
	// output data.
	//
	// The location is not scoped as it's not a meaningful boundary for "human access"
	// to data as clusters may span locations.
	var rulesList monitoringv1alpha1.RulesList
	if err := r.client.List(ctx, &rulesList); err != nil {
		return errors.Wrap(err, "list rules")
	}
	for _, rs := range rulesList.Items {
		result, err := generateRules(&rs, r.opts)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "rules_namespace", rs.Namespace, "rules_name", rs.Name)
		}
		filename := fmt.Sprintf("rules__%s__%s.yaml", rs.Namespace, rs.Name)
		cm.Data[filename] = result
	}

	var clusterRulesList monitoringv1alpha1.ClusterRulesList
	if err := r.client.List(ctx, &clusterRulesList); err != nil {
		return errors.Wrap(err, "list cluster rules")
	}
	for _, rs := range clusterRulesList.Items {
		result, err := generateClusterRules(&rs, r.opts)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "clusterrules_name", rs.Name)
		}
		filename := fmt.Sprintf("clusterrules__%s.yaml", rs.Name)
		cm.Data[filename] = string(result)
	}

	var globalRulesList monitoringv1alpha1.GlobalRulesList
	if err := r.client.List(ctx, &globalRulesList); err != nil {
		return errors.Wrap(err, "list global rules")
	}
	for _, rs := range globalRulesList.Items {
		result, err := generateGlobalRules(&rs)
		if err != nil {
			// TODO(freinartz): update resource condition.
			logger.Error(err, "converting rules failed", "globalrules_name", rs.Name)
		}
		filename := fmt.Sprintf("globalrules__%s.yaml", rs.Name)
		cm.Data[filename] = string(result)
	}

	// Create or update generated rule ConfigMap.
	if err := r.client.Update(ctx, cm); apierrors.IsNotFound(err) {
		if err := r.client.Create(ctx, cm); err != nil {
			return errors.Wrap(err, "create generated rules")
		}
	} else if err != nil {
		return errors.Wrap(err, "update generated rules")
	}
	return nil
}

func generateRules(apiRules *monitoringv1alpha1.Rules, opts Options) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	if err := rules.Scope(&rs, map[string]string{
		export.KeyProjectID: opts.ProjectID,
		export.KeyCluster:   opts.Cluster,
		export.KeyLocation:  opts.Location,
		export.KeyNamespace: apiRules.Namespace,
	}); err != nil {
		return "", errors.Wrap(err, "isolating rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

func generateClusterRules(apiRules *monitoringv1alpha1.ClusterRules, opts Options) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	if err := rules.Scope(&rs, map[string]string{
		export.KeyProjectID: opts.ProjectID,
		export.KeyLocation:  opts.Location,
		export.KeyCluster:   opts.Cluster,
	}); err != nil {
		return "", errors.Wrap(err, "isolating rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

func generateGlobalRules(apiRules *monitoringv1alpha1.GlobalRules) (string, error) {
	rs, err := rules.FromAPIRules(apiRules.Spec.Groups)
	if err != nil {
		return "", errors.Wrap(err, "converting rules failed")
	}
	result, err := yaml.Marshal(rs)
	if err != nil {
		return "", errors.Wrap(err, "marshalling rules failed")
	}
	return string(result), nil
}

type rulesValidator struct {
	opts Options
}

func (v *rulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateRules(o.(*monitoringv1alpha1.Rules), v.opts)
	return err
}

func (v *rulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *rulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}

type clusterRulesValidator struct {
	opts Options
}

func (v *clusterRulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateClusterRules(o.(*monitoringv1alpha1.ClusterRules), v.opts)
	return err
}

func (v *clusterRulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *clusterRulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}

type globalRulesValidator struct{}

func (v *globalRulesValidator) ValidateCreate(ctx context.Context, o runtime.Object) error {
	_, err := generateGlobalRules(o.(*monitoringv1alpha1.GlobalRules))
	return err
}

func (v *globalRulesValidator) ValidateUpdate(ctx context.Context, _, o runtime.Object) error {
	return v.ValidateCreate(ctx, o)
}

func (v *globalRulesValidator) ValidateDelete(ctx context.Context, o runtime.Object) error {
	return nil
}
<|endoftext|>


<commit_before>package responder

import (
	"errors"
	"sync"
	"time"
)

// for testing
var now = time.Now

// ErrNoResponse only returned on Pool.Get() failure.
var ErrNoResponse = errors.New("all responders failed")

// Pool is a pool of Responders mapped by name.
type Pool struct {
	poolMu sync.RWMutex
	pool   map[string]*Responder
}

// NewPool returns an empty Pool.
func NewPool() *Pool {
	return &Pool{
		pool: make(map[string]*Responder),
	}
}

// Add adds responder r to Pool if r is not already in pool.
func (p *Pool) Add(r *Responder) {
	p.poolMu.Lock()

	if _, exists := p.pool[r.Name]; exists {
		p.poolMu.Unlock()
		return
	}

	p.pool[r.Name] = r
	p.poolMu.Unlock()
}

// List returns a slice of current responders.
func (p *Pool) List() []*Responder {
	p.poolMu.RLock()

	ret := make([]*Responder, 0)

	for _, v := range p.pool {
		ret = append(ret, v)
	}

	p.poolMu.RUnlock()
	return ret
}

func get(w, l int) int {
	if w < 1 {
		return 1
	}

	if w <= l {
		return w - 1
	}

	w = w % l
	if w < 1 {
		w = l
	}
	return w - 1
}

func (p *Pool) get(name string, add bool) *Responder {
	p.poolMu.Lock()
	if r, exists := p.pool[name]; exists {
		p.poolMu.Unlock()
		return r
	}
	p.poolMu.Unlock()

	n := &Responder{Name: name}
	if add {
		p.Add(n)
	}

	return n
}

// Get returns ErrNoResponse when all responders are in failed state.
// All non existant elements of r are added to Pool.
func (p *Pool) Get(r []string) (*Responder, int, error) {
	_, w := now().ISOWeek()
	l := len(r)

	if l < 1 {
		return nil, 0, nil
	}

	for x := 0; x < l; x++ {
		i := get(w+x, l)
		u := p.get(r[i], true)

		if !u.Failed() {
			return u, i, nil
		}
	}

	return nil, 0, ErrNoResponse
}

// Update increments the tickcounter for name.
// Does not add name to pool.
func (p *Pool) Update(name string) {
	r := p.get(name, false)
	r.Update()
}

// ResetFailed resets failed state for name,
// returns true if name was in a failed state.
// Adds name to pool if it does not exist.
func (p *Pool) ResetFailed(name string) bool {
	r := p.get(name, true)
	return r.resetFailed()
}
<commit_msg>responder: return correct index<commit_after>package responder

import (
	"errors"
	"sync"
	"time"
)

// for testing
var now = time.Now

// ErrNoResponse only returned on Pool.Get() failure.
var ErrNoResponse = errors.New("all responders failed")

// Pool is a pool of Responders mapped by name.
type Pool struct {
	poolMu sync.RWMutex
	pool   map[string]*Responder
}

// NewPool returns an empty Pool.
func NewPool() *Pool {
	return &Pool{
		pool: make(map[string]*Responder),
	}
}

// Add adds responder r to Pool if r is not already in pool.
func (p *Pool) Add(r *Responder) {
	p.poolMu.Lock()

	if _, exists := p.pool[r.Name]; exists {
		p.poolMu.Unlock()
		return
	}

	p.pool[r.Name] = r
	p.poolMu.Unlock()
}

// List returns a slice of current responders.
func (p *Pool) List() []*Responder {
	p.poolMu.RLock()

	ret := make([]*Responder, 0)

	for _, v := range p.pool {
		ret = append(ret, v)
	}

	p.poolMu.RUnlock()
	return ret
}

func get(w, l int) int {
	if w < 1 {
		return 0
	}

	if w <= l {
		return w - 1
	}

	w = w % l
	if w < 1 {
		w = l
	}
	return w - 1
}

func (p *Pool) get(name string, add bool) *Responder {
	p.poolMu.Lock()
	if r, exists := p.pool[name]; exists {
		p.poolMu.Unlock()
		return r
	}
	p.poolMu.Unlock()

	n := &Responder{Name: name}
	if add {
		p.Add(n)
	}

	return n
}

// Get returns ErrNoResponse when all responders are in failed state.
// All non existant elements of r are added to Pool.
func (p *Pool) Get(r []string) (*Responder, int, error) {
	_, w := now().ISOWeek()
	l := len(r)

	if l < 1 {
		return nil, 0, nil
	}

	for x := 0; x < l; x++ {
		i := get(w+x, l)
		u := p.get(r[i], true)

		if !u.Failed() {
			return u, i, nil
		}
	}

	return nil, 0, ErrNoResponse
}

// Update increments the tickcounter for name.
// Does not add name to pool.
func (p *Pool) Update(name string) {
	r := p.get(name, false)
	r.Update()
}

// ResetFailed resets failed state for name,
// returns true if name was in a failed state.
// Adds name to pool if it does not exist.
func (p *Pool) ResetFailed(name string) bool {
	r := p.get(name, true)
	return r.resetFailed()
}
<|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 server

import (
	"bytes"
	"fmt"
	"net/http"
	rt "runtime"
	"sort"
	"strings"

	"github.com/emicklei/go-restful"
	"k8s.io/klog/v2"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	"k8s.io/apiserver/pkg/server/mux"
)

// APIServerHandlers holds the different http.Handlers used by the API server.
// This includes the full handler chain, the director (which chooses between gorestful and nonGoRestful,
// the gorestful handler (used for the API) which falls through to the nonGoRestful handler on unregistered paths,
// and the nonGoRestful handler (which can contain a fallthrough of its own)
// FullHandlerChain -> Director -> {GoRestfulContainer,NonGoRestfulMux} based on inspection of registered web services
type APIServerHandler struct {
	// FullHandlerChain is the one that is eventually served with.  It should include the full filter
	// chain and then call the Director.
	FullHandlerChain http.Handler
	// The registered APIs.  InstallAPIs uses this.  Other servers probably shouldn't access this directly.
	GoRestfulContainer *restful.Container
	// NonGoRestfulMux is the final HTTP handler in the chain.
	// It comes after all filters and the API handling
	// This is where other servers can attach handler to various parts of the chain.
	NonGoRestfulMux *mux.PathRecorderMux

	// Director is here so that we can properly handle fall through and proxy cases.
	// This looks a bit bonkers, but here's what's happening.  We need to have /apis handling registered in gorestful in order to have
	// swagger generated for compatibility.  Doing that with `/apis` as a webservice, means that it forcibly 404s (no defaulting allowed)
	// all requests which are not /apis or /apis/.  We need those calls to fall through behind goresful for proper delegation.  Trying to
	// register for a pattern which includes everything behind it doesn't work because gorestful negotiates for verbs and content encoding
	// and all those things go crazy when gorestful really just needs to pass through.  In addition, openapi enforces unique verb constraints
	// which we don't fit into and it still muddies up swagger.  Trying to switch the webservices into a route doesn't work because the
	//  containing webservice faces all the same problems listed above.
	// This leads to the crazy thing done here.  Our mux does what we need, so we'll place it in front of gorestful.  It will introspect to
	// decide if the route is likely to be handled by goresful and route there if needed.  Otherwise, it goes to PostGoRestful mux in
	// order to handle "normal" paths and delegation. Hopefully no API consumers will ever have to deal with this level of detail.  I think
	// we should consider completely removing gorestful.
	// Other servers should only use this opaquely to delegate to an API server.
	Director http.Handler
}

// HandlerChainBuilderFn is used to wrap the GoRestfulContainer handler using the provided handler chain.
// It is normally used to apply filtering like authentication and authorization
type HandlerChainBuilderFn func(apiHandler http.Handler) http.Handler

func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
	nonGoRestfulMux := mux.NewPathRecorderMux(name)
	if notFoundHandler != nil {
		nonGoRestfulMux.NotFoundHandler(notFoundHandler)
	}

	gorestfulContainer := restful.NewContainer()
	gorestfulContainer.ServeMux = http.NewServeMux()
	gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
	gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
		logStackOnRecover(s, panicReason, httpWriter)
	})
	gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
		serviceErrorHandler(s, serviceErr, request, response)
	})

	director := director{
		name:               name,
		goRestfulContainer: gorestfulContainer,
		nonGoRestfulMux:    nonGoRestfulMux,
	}

	return &APIServerHandler{
		FullHandlerChain:   handlerChainBuilder(director),
		GoRestfulContainer: gorestfulContainer,
		NonGoRestfulMux:    nonGoRestfulMux,
		Director:           director,
	}
}

// ListedPaths returns the paths that should be shown under /
func (a *APIServerHandler) ListedPaths() []string {
	var handledPaths []string
	// Extract the paths handled using restful.WebService
	for _, ws := range a.GoRestfulContainer.RegisteredWebServices() {
		handledPaths = append(handledPaths, ws.RootPath())
	}
	handledPaths = append(handledPaths, a.NonGoRestfulMux.ListedPaths()...)
	sort.Strings(handledPaths)

	return handledPaths
}

type director struct {
	name               string
	goRestfulContainer *restful.Container
	nonGoRestfulMux    *mux.PathRecorderMux
}

func (d director) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Path

	// check to see if our webservices want to claim this path
	for _, ws := range d.goRestfulContainer.RegisteredWebServices() {
		switch {
		case ws.RootPath() == "/apis":
			// if we are exactly /apis or /apis/, then we need special handling in loop.
			// normally these are passed to the nonGoRestfulMux, but if discovery is enabled, it will go directly.
			// We can't rely on a prefix match since /apis matches everything (see the big comment on Director above)
			if path == "/apis" || path == "/apis/" {
				klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
				// TODO fix gorestful, remove TPRs, or stop using gorestful
				d.goRestfulContainer.Dispatch(w, req)
				return
			}

		case strings.HasPrefix(path, ws.RootPath()):
			// ensure an exact match or a path boundary match
			if len(path) == len(ws.RootPath()) || path[len(ws.RootPath())] == '/' {
				klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
				// TODO fix gorestful, remove TPRs, or stop using gorestful
				d.goRestfulContainer.Dispatch(w, req)
				return
			}
		}
	}

	// if we didn't find a match, then we just skip gorestful altogether
	klog.V(5).Infof("%v: %v %q satisfied by nonGoRestful", d.name, req.Method, path)
	d.nonGoRestfulMux.ServeHTTP(w, req)
}

//TODO: Unify with RecoverPanics?
func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
	for i := 2; ; i++ {
		_, file, line, ok := rt.Caller(i)
		if !ok {
			break
		}
		buffer.WriteString(fmt.Sprintf("    %s:%d\r\n", file, line))
	}
	klog.Errorln(buffer.String())

	headers := http.Header{}
	if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
		headers.Set("Accept", ct)
	}
	responsewriters.ErrorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", schema.GroupResource{}, "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
}

func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
	responsewriters.ErrorNegotiated(
		apierrors.NewGenericServerResponse(serviceErr.Code, "", schema.GroupResource{}, "", serviceErr.Message, 0, false),
		s,
		schema.GroupVersion{},
		resp,
		request.Request,
	)
}

// ServeHTTP makes it an http.Handler
func (a *APIServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	a.FullHandlerChain.ServeHTTP(w, r)
}
<commit_msg>k8s.io/apiserver/pkg/server: fix name in APIServerHandler description<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 server

import (
	"bytes"
	"fmt"
	"net/http"
	rt "runtime"
	"sort"
	"strings"

	"github.com/emicklei/go-restful"
	"k8s.io/klog/v2"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	"k8s.io/apiserver/pkg/server/mux"
)

// APIServerHandlers holds the different http.Handlers used by the API server.
// This includes the full handler chain, the director (which chooses between gorestful and nonGoRestful,
// the gorestful handler (used for the API) which falls through to the nonGoRestful handler on unregistered paths,
// and the nonGoRestful handler (which can contain a fallthrough of its own)
// FullHandlerChain -> Director -> {GoRestfulContainer,NonGoRestfulMux} based on inspection of registered web services
type APIServerHandler struct {
	// FullHandlerChain is the one that is eventually served with.  It should include the full filter
	// chain and then call the Director.
	FullHandlerChain http.Handler
	// The registered APIs.  InstallAPIs uses this.  Other servers probably shouldn't access this directly.
	GoRestfulContainer *restful.Container
	// NonGoRestfulMux is the final HTTP handler in the chain.
	// It comes after all filters and the API handling
	// This is where other servers can attach handler to various parts of the chain.
	NonGoRestfulMux *mux.PathRecorderMux

	// Director is here so that we can properly handle fall through and proxy cases.
	// This looks a bit bonkers, but here's what's happening.  We need to have /apis handling registered in gorestful in order to have
	// swagger generated for compatibility.  Doing that with `/apis` as a webservice, means that it forcibly 404s (no defaulting allowed)
	// all requests which are not /apis or /apis/.  We need those calls to fall through behind goresful for proper delegation.  Trying to
	// register for a pattern which includes everything behind it doesn't work because gorestful negotiates for verbs and content encoding
	// and all those things go crazy when gorestful really just needs to pass through.  In addition, openapi enforces unique verb constraints
	// which we don't fit into and it still muddies up swagger.  Trying to switch the webservices into a route doesn't work because the
	//  containing webservice faces all the same problems listed above.
	// This leads to the crazy thing done here.  Our mux does what we need, so we'll place it in front of gorestful.  It will introspect to
	// decide if the route is likely to be handled by goresful and route there if needed.  Otherwise, it goes to NonGoRestfulMux mux in
	// order to handle "normal" paths and delegation. Hopefully no API consumers will ever have to deal with this level of detail.  I think
	// we should consider completely removing gorestful.
	// Other servers should only use this opaquely to delegate to an API server.
	Director http.Handler
}

// HandlerChainBuilderFn is used to wrap the GoRestfulContainer handler using the provided handler chain.
// It is normally used to apply filtering like authentication and authorization
type HandlerChainBuilderFn func(apiHandler http.Handler) http.Handler

func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
	nonGoRestfulMux := mux.NewPathRecorderMux(name)
	if notFoundHandler != nil {
		nonGoRestfulMux.NotFoundHandler(notFoundHandler)
	}

	gorestfulContainer := restful.NewContainer()
	gorestfulContainer.ServeMux = http.NewServeMux()
	gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
	gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
		logStackOnRecover(s, panicReason, httpWriter)
	})
	gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
		serviceErrorHandler(s, serviceErr, request, response)
	})

	director := director{
		name:               name,
		goRestfulContainer: gorestfulContainer,
		nonGoRestfulMux:    nonGoRestfulMux,
	}

	return &APIServerHandler{
		FullHandlerChain:   handlerChainBuilder(director),
		GoRestfulContainer: gorestfulContainer,
		NonGoRestfulMux:    nonGoRestfulMux,
		Director:           director,
	}
}

// ListedPaths returns the paths that should be shown under /
func (a *APIServerHandler) ListedPaths() []string {
	var handledPaths []string
	// Extract the paths handled using restful.WebService
	for _, ws := range a.GoRestfulContainer.RegisteredWebServices() {
		handledPaths = append(handledPaths, ws.RootPath())
	}
	handledPaths = append(handledPaths, a.NonGoRestfulMux.ListedPaths()...)
	sort.Strings(handledPaths)

	return handledPaths
}

type director struct {
	name               string
	goRestfulContainer *restful.Container
	nonGoRestfulMux    *mux.PathRecorderMux
}

func (d director) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Path

	// check to see if our webservices want to claim this path
	for _, ws := range d.goRestfulContainer.RegisteredWebServices() {
		switch {
		case ws.RootPath() == "/apis":
			// if we are exactly /apis or /apis/, then we need special handling in loop.
			// normally these are passed to the nonGoRestfulMux, but if discovery is enabled, it will go directly.
			// We can't rely on a prefix match since /apis matches everything (see the big comment on Director above)
			if path == "/apis" || path == "/apis/" {
				klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
				// TODO fix gorestful, remove TPRs, or stop using gorestful
				d.goRestfulContainer.Dispatch(w, req)
				return
			}

		case strings.HasPrefix(path, ws.RootPath()):
			// ensure an exact match or a path boundary match
			if len(path) == len(ws.RootPath()) || path[len(ws.RootPath())] == '/' {
				klog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
				// TODO fix gorestful, remove TPRs, or stop using gorestful
				d.goRestfulContainer.Dispatch(w, req)
				return
			}
		}
	}

	// if we didn't find a match, then we just skip gorestful altogether
	klog.V(5).Infof("%v: %v %q satisfied by nonGoRestful", d.name, req.Method, path)
	d.nonGoRestfulMux.ServeHTTP(w, req)
}

//TODO: Unify with RecoverPanics?
func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
	for i := 2; ; i++ {
		_, file, line, ok := rt.Caller(i)
		if !ok {
			break
		}
		buffer.WriteString(fmt.Sprintf("    %s:%d\r\n", file, line))
	}
	klog.Errorln(buffer.String())

	headers := http.Header{}
	if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
		headers.Set("Accept", ct)
	}
	responsewriters.ErrorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", schema.GroupResource{}, "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
}

func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
	responsewriters.ErrorNegotiated(
		apierrors.NewGenericServerResponse(serviceErr.Code, "", schema.GroupResource{}, "", serviceErr.Message, 0, false),
		s,
		schema.GroupVersion{},
		resp,
		request.Request,
	)
}

// ServeHTTP makes it an http.Handler
func (a *APIServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	a.FullHandlerChain.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package common_filters

import (
	"github.com/graphql-go/graphql"
)

// The filters common to Local->Get and Local->GetMeta queries.
var CommonFilters graphql.InputObjectConfigFieldMap = graphql.InputObjectConfigFieldMap{
	"operator": &graphql.InputObjectFieldConfig{
		Type: graphql.NewEnum(graphql.EnumConfig{
			Name: "WhereOperatorEnum",
			Values: graphql.EnumValueConfigMap{
				"And":              &graphql.EnumValueConfig{},
				"Or":               &graphql.EnumValueConfig{},
				"Equal":            &graphql.EnumValueConfig{},
				"Not":              &graphql.EnumValueConfig{},
				"NotEqual":         &graphql.EnumValueConfig{},
				"GreaterThan":      &graphql.EnumValueConfig{},
				"GreaterThanEqual": &graphql.EnumValueConfig{},
				"LessThan":         &graphql.EnumValueConfig{},
				"LessThanEqual":    &graphql.EnumValueConfig{},
			},
			Description: "Enumeration object for the 'where' filter",
		}),
		Description: "Operator in the 'where' filter field, value is one of the 'WhereOperatorEnum' object",
	},
	"path": &graphql.InputObjectFieldConfig{
		Type:        graphql.NewList(graphql.String),
		Description: "Path of from 'Things' or 'Actions' to the property name through the classes",
	},
	"valueInt": &graphql.InputObjectFieldConfig{
		Type:        graphql.Int,
		Description: "Integer value that the property at the provided path will be compared to by an operator",
	},
	"valueNumber": &graphql.InputObjectFieldConfig{
		Type:        graphql.Float,
		Description: "Number value that the property at the provided path will be compared to by an operator",
	},
	"valueBoolean": &graphql.InputObjectFieldConfig{
		Type:        graphql.Boolean,
		Description: "Boolean value that the property at the provided path will be compared to by an operator",
	},
	"valueString": &graphql.InputObjectFieldConfig{
		Type:        graphql.String,
		Description: "String value that the property at the provided path will be compared to by an operator",
	},
	"valueDate": &graphql.InputObjectFieldConfig{
		Type:        graphql.String,
		Description: "String value that the property at the provided path will be compared to by an operator",
	},
}

// We need to bootstrap the operands back to the commonfilters
func init() {
	var operands *graphql.InputObject

	operands = graphql.NewInputObject(
		graphql.InputObjectConfig{
			Name: "WhereOperandsInpObj",
			Fields: (graphql.InputObjectConfigFieldMapThunk)(func() graphql.InputObjectConfigFieldMap {
				CommonFilters["operands"] = &graphql.InputObjectFieldConfig{
					Type:        graphql.NewList(operands),
					Description: "Operands in the 'where' filter field, is a list of objects",
				}
				return CommonFilters
			}),
			Description: "Operands in the 'where' filter field, is a list of objects",
		},
	)

	CommonFilters["operands"] = &graphql.InputObjectFieldConfig{
		Type:        graphql.NewList(operands),
		Description: "Operands in the 'where' filter field, is a list of objects",
	}
}
<commit_msg>Remove concurrency bug from thunk in filters<commit_after>package common_filters

import (
	"github.com/graphql-go/graphql"
	"sync"
)

// The filters common to Local->Get and Local->GetMeta queries.
var CommonFilters graphql.InputObjectConfigFieldMap = graphql.InputObjectConfigFieldMap{
	"operator": &graphql.InputObjectFieldConfig{
		Type: graphql.NewEnum(graphql.EnumConfig{
			Name: "WhereOperatorEnum",
			Values: graphql.EnumValueConfigMap{
				"And":              &graphql.EnumValueConfig{},
				"Or":               &graphql.EnumValueConfig{},
				"Equal":            &graphql.EnumValueConfig{},
				"Not":              &graphql.EnumValueConfig{},
				"NotEqual":         &graphql.EnumValueConfig{},
				"GreaterThan":      &graphql.EnumValueConfig{},
				"GreaterThanEqual": &graphql.EnumValueConfig{},
				"LessThan":         &graphql.EnumValueConfig{},
				"LessThanEqual":    &graphql.EnumValueConfig{},
			},
			Description: "Enumeration object for the 'where' filter",
		}),
		Description: "Operator in the 'where' filter field, value is one of the 'WhereOperatorEnum' object",
	},
	"path": &graphql.InputObjectFieldConfig{
		Type:        graphql.NewList(graphql.String),
		Description: "Path of from 'Things' or 'Actions' to the property name through the classes",
	},
	"valueInt": &graphql.InputObjectFieldConfig{
		Type:        graphql.Int,
		Description: "Integer value that the property at the provided path will be compared to by an operator",
	},
	"valueNumber": &graphql.InputObjectFieldConfig{
		Type:        graphql.Float,
		Description: "Number value that the property at the provided path will be compared to by an operator",
	},
	"valueBoolean": &graphql.InputObjectFieldConfig{
		Type:        graphql.Boolean,
		Description: "Boolean value that the property at the provided path will be compared to by an operator",
	},
	"valueString": &graphql.InputObjectFieldConfig{
		Type:        graphql.String,
		Description: "String value that the property at the provided path will be compared to by an operator",
	},
	"valueDate": &graphql.InputObjectFieldConfig{
		Type:        graphql.String,
		Description: "String value that the property at the provided path will be compared to by an operator",
	},
}

// Used to setup the link between operands, and the
var defineOnce sync.Once

// We need to bootstrap the operands back to the commonfilters
func init() {
	var operands *graphql.InputObject

	operands = graphql.NewInputObject(
		graphql.InputObjectConfig{
			Name: "WhereOperandsInpObj",
			Fields: (graphql.InputObjectConfigFieldMapThunk)(func() graphql.InputObjectConfigFieldMap {
				// Set up this link just once.
				defineOnce.Do(func() {
					CommonFilters["operands"] = &graphql.InputObjectFieldConfig{
						Type:        graphql.NewList(operands),
						Description: "Operands in the 'where' filter field, is a list of objects",
					}
				})
				return CommonFilters
			}),
			Description: "Operands in the 'where' filter field, is a list of objects",
		},
	)

	CommonFilters["operands"] = &graphql.InputObjectFieldConfig{
		Type:        graphql.NewList(operands),
		Description: "Operands in the 'where' filter field, is a list of objects",
	}
}
<|endoftext|>


<commit_before>/*
 Copyright 2018 Padduck, LLC
 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 web

import (
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/pufferpanel/v2/middleware"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/api"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/auth"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/daemon"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/handlers"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/oauth2"
	"github.com/spf13/viper"
	"net/http"
	"strings"
)

var ClientPath string
var IndexFile string

var noHandle404 = []string{"/api/", "/oauth2/", "/daemon/"}

func RegisterRoutes(e *gin.Engine) {
	e.Use(func(c *gin.Context) {
		middleware.Recover(c)
	})

	ClientPath = viper.GetString("panel.web.files")
	IndexFile = ClientPath + "/index.html"

	api.RegisterRoutes(e.Group("/api", handlers.HasOAuth2Token))
	e.GET("/api/config", config)
	oauth2.RegisterRoutes(e.Group("/oauth2"))
	auth.RegisterRoutes(e.Group("/auth"))
	daemon.RegisterRoutes(e.Group("/daemon", handlers.HasOAuth2Token))

	e.Static("/css", ClientPath+"/css")
	e.Static("/fonts", ClientPath+"/fonts")
	e.Static("/img", ClientPath+"/img")
	e.Static("/js", ClientPath+"/js")
	e.StaticFile("/favicon.png", ClientPath+"/favicon.png")
	e.StaticFile("/favicon.ico", ClientPath+"/favicon.ico")
	//e.StaticFile("/", IndexFile)
	e.NoRoute( /*handlers.AuthMiddleware,*/ handle404)
}

func handle404(c *gin.Context) {
	for _, v := range noHandle404 {
		if strings.HasPrefix(c.Request.URL.Path, v) {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}
	}

	if strings.HasSuffix(c.Request.URL.Path, ".js") {
		c.Writer.Header().Set("Content-Type", "application/js")
		c.File(ClientPath + c.Request.URL.Path)
		return
	}

	if strings.HasSuffix(c.Request.URL.Path, ".css") {
		c.Writer.Header().Set("Content-Type", "application/css")
		c.File(ClientPath + c.Request.URL.Path)
		return
	}

	c.File(IndexFile)
}

//TODO: Have this pull from somewhere else
func config(c *gin.Context) {
	c.JSON(http.StatusOK, map[string]interface{}{
		"branding": map[string]interface{}{
			"name": "PufferPanel",
		},
	})
}
<commit_msg>Clean up content-type handling, remove dev-only route handlers<commit_after>/*
 Copyright 2018 Padduck, LLC
 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 web

import (
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/pufferpanel/v2/middleware"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/api"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/auth"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/daemon"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/handlers"
	"github.com/pufferpanel/pufferpanel/v2/panel/web/oauth2"
	"github.com/spf13/viper"
	"net/http"
	"strings"
)

var ClientPath string
var IndexFile string

var noHandle404 = []string{"/api/", "/oauth2/", "/daemon/"}

func RegisterRoutes(e *gin.Engine) {
	e.Use(func(c *gin.Context) {
		middleware.Recover(c)
	})

	ClientPath = viper.GetString("panel.web.files")
	IndexFile = ClientPath + "/index.html"

	api.RegisterRoutes(e.Group("/api", handlers.HasOAuth2Token))
	e.GET("/api/config", config)
	oauth2.RegisterRoutes(e.Group("/oauth2"))
	auth.RegisterRoutes(e.Group("/auth"))
	daemon.RegisterRoutes(e.Group("/daemon", handlers.HasOAuth2Token))

	css := e.Group("/css")
	{
		css.StaticFS("", http.Dir(ClientPath+"/css"))
	}
	fonts := e.Group("/fonts")
	{
		fonts.StaticFS("", http.Dir(ClientPath+"/fonts"))
	}
	img := e.Group("/img")
	{
		img.StaticFS("", http.Dir(ClientPath+"/img"))
	}
	js := e.Group("/js", setContentType("application/javascript"))
	{
		js.StaticFS("", http.Dir(ClientPath+"/js"))
	}
	e.StaticFile("/favicon.png", ClientPath+"/favicon.png")
	e.StaticFile("/favicon.ico", ClientPath+"/favicon.ico")
	//e.StaticFile("/", IndexFile)
	e.NoRoute( /*handlers.AuthMiddleware,*/ handle404)
}

func handle404(c *gin.Context) {
	for _, v := range noHandle404 {
		if strings.HasPrefix(c.Request.URL.Path, v) {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}
	}

	c.File(IndexFile)
}

//TODO: Have this pull from somewhere else
func config(c *gin.Context) {
	c.JSON(http.StatusOK, map[string]interface{}{
		"branding": map[string]interface{}{
			"name": "PufferPanel",
		},
	})
}

func setContentType(contentType string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Content-Type", contentType)
		c.Next()
	}
}
<|endoftext|>