<commit_before>package main

import (
    "web"
    "strings"
    "godis"
    "fmt"
)

const(
    // characters used for short-urls
    SYMBOLS = "0123456789abcdefghijklmnopqrsuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
    // special key in redis, that is our global counter
    COUNTER = "__counter__"
    HTTP = "http"
)

// connecting to redis on localhost, db with id 0 and no password
var (
    redis = godis.New("", 0, "")
)

// function to resolve a shorturl and redirect
func resolve(ctx *web.Context, short string) {
    redirect, _ := redis.Get(short)
    ctx.Redirect(302, redirect.String())
    // TODO needs error handling here
}

// function to shorten and store a url
func shorten(ctx *web.Context, data string){
    const jsntmpl = "{\"url\" : \"%s\", \"longurl\" : \"%s\"}\n"
    if url, ok := ctx.Request.Params["url"]; ok{
        if ! strings.HasPrefix(url, HTTP){
            url = fmt.Sprintf("%s://%s", HTTP, url)
        }
        ctr, _ := redis.Incr(COUNTER)
        encoded := encode(ctr)
        go redis.Set(encoded, url)
        request := ctx.Request
        ctx.SetHeader("Content-Type", "application/json", true)
        host := request.Host
        if realhost, ok := ctx.Request.Params["X-Real-IP"]; ok{
            host = realhost
        }
        location := fmt.Sprintf("%s://%s/%s", HTTP, host, encoded)
        ctx.SetHeader("Location", location, true)
        ctx.StartResponse(201)
        ctx.WriteString(fmt.Sprintf(jsntmpl, location, url))
    }else{
       ctx.Redirect(404, "/")
    }
}

// encodes a number into our *base* representation
// TODO can this be made better with some bitshifting?
func encode(number int64) string{
    const base = int64(len(SYMBOLS))
    rest := number % base
    // strings are a bit weird in go...
    result := string(SYMBOLS[rest])
    if number - rest != 0{
       newnumber := (number - rest ) / base
       result = encode(newnumber) + result
    }
    return result
}

// main function that inits the routes in web.go
func main() {
    web.Post("/shorten/(.*)", shorten)
    web.Get("/(.*)", resolve)
    web.Run("0.0.0.0:9999")
}

<commit_msg>handle non existing short url 'gracefully' :-)<commit_after>package main

import (
    "web"
    "strings"
    "godis"
    "fmt"
)

const(
    // characters used for short-urls
    SYMBOLS = "0123456789abcdefghijklmnopqrsuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
    // special key in redis, that is our global counter
    COUNTER = "__counter__"
    HTTP = "http"
)

// connecting to redis on localhost, db with id 0 and no password
var (
    redis = godis.New("", 0, "")
)

// function to resolve a shorturl and redirect
func resolve(ctx *web.Context, short string) {
    redirect, err := redis.Get(short)
    if err == nil {
        ctx.Redirect(301, redirect.String())
    } else {
        ctx.Redirect(301, "https://www.youtube.com/watch?v=jRHmvy5eaG4")
    }
}

// function to shorten and store a url
func shorten(ctx *web.Context, data string){
    const jsntmpl = "{\"url\" : \"%s\", \"longurl\" : \"%s\"}\n"
    if url, ok := ctx.Request.Params["url"]; ok{
        if ! strings.HasPrefix(url, HTTP){
            url = fmt.Sprintf("%s://%s", HTTP, url)
        }
        ctr, _ := redis.Incr(COUNTER)
        encoded := encode(ctr)
        go redis.Set(encoded, url)
        request := ctx.Request
        ctx.SetHeader("Content-Type", "application/json", true)
        host := request.Host
        if realhost, ok := ctx.Request.Params["X-Real-IP"]; ok{
            host = realhost
        }
        location := fmt.Sprintf("%s://%s/%s", HTTP, host, encoded)
        ctx.SetHeader("Location", location, true)
        ctx.StartResponse(201)
        ctx.WriteString(fmt.Sprintf(jsntmpl, location, url))
    }else{
       ctx.Redirect(404, "/")
    }
}

// encodes a number into our *base* representation
// TODO can this be made better with some bitshifting?
func encode(number int64) string{
    const base = int64(len(SYMBOLS))
    rest := number % base
    // strings are a bit weird in go...
    result := string(SYMBOLS[rest])
    if number - rest != 0{
       newnumber := (number - rest ) / base
       result = encode(newnumber) + result
    }
    return result
}

// main function that inits the routes in web.go
func main() {
    web.Post("/shorten/(.*)", shorten)
    web.Get("/(.*)", resolve)
    web.Run("0.0.0.0:9999")
}

<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

func ReverseWords(stringR string) []string {
	indexSpace = make([]int, 0)
	var wordsList []string

	for i, s := range stringR {
		if s == " " {
			indexSpace = append(indexSpace, i)
		}
	}
	for i, _ := range indexSpace {
		if i < len(indexSpace)-1; indexSpace[i+1]-indexSpace[i] >= 2 {
			wordsList = append(wordsList, stringR[indexSpace[i]:indexSpace[i+1]])
		}
	}
}
<commit_msg>find a new way to split string<commit_after>package main

import (
	"fmt"
	"strings"
)

func ReverseWords(stringR string) []string {
	//indexSpace = make([]int, 0)
	var wordsList []string
	/**
	for i, s := range stringR {
		if s == " " {
			indexSpace = append(indexSpace, i)
		}
	}
	for i, _ := range indexSpace {
		if i < len(indexSpace)-1; indexSpace[i+1]-indexSpace[i] >= 2 {
			wordsList = append(wordsList, stringR[indexSpace[i]:indexSpace[i+1]])
		}
	}*/

	wordsList = strings.Split(stringR, " ")
	return wordsList
}

func main() {
	fmt.Print(ReverseWords("ccQ nihao"))
}
<|endoftext|>


<commit_before>package flag_test

import (
	. "code.cloudfoundry.org/cli/command/flag"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("TrimmedString", func() {
	var trimmedString TrimmedString

	Describe("default value", func() {
		It("has an empty value", func() {
			Expect(trimmedString).To(BeEmpty())
		})
	})

	Describe("UnmarshalFlag", func() {
		BeforeEach(func() {
			err := trimmedString.UnmarshalFlag("   some string   ")
			Expect(err).NotTo(HaveOccurred())
		})

		It("has the right value", func() {
			Expect(trimmedString).To(BeEquivalentTo("some string"))
		})
	})
})
<commit_msg>fix: remove unnecessary test<commit_after>package flag_test

import (
	. "code.cloudfoundry.org/cli/command/flag"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("TrimmedString", func() {
	var trimmedString TrimmedString

	Describe("UnmarshalFlag", func() {
		BeforeEach(func() {
			err := trimmedString.UnmarshalFlag("   some string   ")
			Expect(err).NotTo(HaveOccurred())
		})

		It("has the right value", func() {
			Expect(trimmedString).To(BeEquivalentTo("some string"))
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
  "os"
  "fmt"
  "time"
  "log"
  "path"
  "flag"
  "runtime"
  "io/ioutil"
  "net/http"
  "encoding/json"
  "github.com/fzzy/radix/redis"
)

var redis_location = flag.String("redis", "127.0.0.1:6379", "Location of redis instance")
var server_bind = flag.String("bind", "127.0.0.1:5000", "Location server should listen at")

var client *redis.Client

type RequestInfo struct {
  Query string
  UserAgent string
  Time int64
}

func initStorage(db int){
  var err error
  client, err = redis.DialTimeout("tcp", *redis_location, time.Duration(10)*time.Second)
  errHndlr(err)
  client.Cmd("SELECT", db)
}

func errHndlr(err error) {
  if err != nil {
    fmt.Println("error:", err)
    os.Exit(1)
  }
}

func httpStore(res http.ResponseWriter, req *http.Request) {
  ri := RequestInfo{
    Query: req.URL.RawQuery,
    UserAgent: req.UserAgent(),
    Time: time.Now().Unix(),
  }

  if ri.Query != ""{
    b, err := json.Marshal(ri)
    errHndlr(err)
    client.Cmd("RPUSH", "incoming", string(b))
  }

  _, filename, _, _ := runtime.Caller(0)
  beacon, err := ioutil.ReadFile(path.Join(path.Dir(filename), "../assets/1x1.gif"))
  errHndlr(err)

  res.Header().Set("Content-Type", "image/gif")
  res.Write(beacon)
}

func main(){
  flag.Parse()
  initStorage(1)
  http.HandleFunc("/", httpStore)
	err := http.ListenAndServe(*server_bind, nil)
  errHndlr(err)
  defer client.Close()
}
<commit_msg>Making the handler mount point controlable through a flag<commit_after>package main

import (
  "os"
  "fmt"
  "time"
  "log"
  "path"
  "flag"
  "runtime"
  "io/ioutil"
  "net/http"
  "encoding/json"
  "github.com/fzzy/radix/redis"
)

var redis_location = flag.String("redis", "127.0.0.1:6379", "Location of redis instance")
var server_bind = flag.String("bind", "127.0.0.1:5000", "Location server should listen at")
var handler_mount = flag.String("mount", "/", "Relative path where handler should be at")

var client *redis.Client

type RequestInfo struct {
  Query string
  UserAgent string
  Time int64
}

func initStorage(db int){
  var err error
  client, err = redis.DialTimeout("tcp", *redis_location, time.Duration(10)*time.Second)
  errHndlr(err)
  client.Cmd("SELECT", db)
}

func errHndlr(err error) {
  if err != nil {
    fmt.Println("error:", err)
    os.Exit(1)
  }
}

func httpStore(res http.ResponseWriter, req *http.Request) {
  ri := RequestInfo{
    Query: req.URL.RawQuery,
    UserAgent: req.UserAgent(),
    Time: time.Now().Unix(),
  }

  if ri.Query != ""{
    b, err := json.Marshal(ri)
    errHndlr(err)
    client.Cmd("RPUSH", "incoming", string(b))
  }

  _, filename, _, _ := runtime.Caller(0)
  beacon, err := ioutil.ReadFile(path.Join(path.Dir(filename), "../assets/1x1.gif"))
  errHndlr(err)

  res.Header().Set("Content-Type", "image/gif")
  res.Write(beacon)
}

func main(){
  flag.Parse()
  initStorage(1)
  http.HandleFunc(*handler_mount, httpStore)
	err := http.ListenAndServe(*server_bind, nil)
  errHndlr(err)
  defer client.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"net/http"
	"time"
)

// Logger is a logging middleware. Path, request time and Method are recorded
func Logger(inner http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		inner.ServeHTTP(w, r)

		log.Printf(
			"%s\t%s\t%s\t%s",
			r.RemoteAddr,
			r.Method,
			r.RequestURI,
			time.Since(start),
		)
	})
}

func main() {
	var (
		dir = flag.String("path", "public", "Path to static site")
	)

	fs := http.FileServer(http.Dir(*dir))
	fs = Logger(fs)
	http.Handle("/", fs)

	log.Println("Listening...")
	http.ListenAndServe(":3000", nil)
}
<commit_msg>use x-forwarded-for to get ip<commit_after>package main

import (
	"flag"
	"log"
	"net/http"
	"time"
)

// Logger is a logging middleware. Path, request time and Method are recorded
func Logger(inner http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		inner.ServeHTTP(w, r)

		log.Printf(
			"%s\t%s\t%s\t%s",
			r.Header.Get("X-Forwarded-For"),
			r.Method,
			r.RequestURI,
			time.Since(start),
		)
	})
}

func main() {
	var (
		dir = flag.String("path", "public", "Path to static site")
	)

	fs := http.FileServer(http.Dir(*dir))
	fs = Logger(fs)
	http.Handle("/", fs)

	log.Println("Listening...")
	http.ListenAndServe(":3000", nil)
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package server

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/ondemand"
	ws "github.com/skydive-project/skydive/websocket"
)

type activeTask struct {
	graph    *graph.Graph
	node     *graph.Node
	resource types.Resource
	task     ondemand.Task
	handler  OnDemandServerHandler
}

// OnDemandServer describes an ondemand task server based on websocket
type OnDemandServer struct {
	common.RWMutex
	graph.DefaultGraphListener
	ws.DefaultSpeakerEventHandler
	Graph        *graph.Graph
	clientPool   *ws.StructClientPool
	activeTasks  map[graph.Identifier]map[string]*activeTask
	wsNamespace  string
	resourceName string
	handler      OnDemandServerHandler
}

// OnDemandServerHandler is the interface to be implemented by ondemand servers
type OnDemandServerHandler interface {
	ResourceName() string
	DecodeMessage(msg json.RawMessage) (types.Resource, error)
	CreateTask(*graph.Node, types.Resource) (interface{}, error)
	RemoveTask(*graph.Node, types.Resource, interface{}) error
}

func (o *OnDemandServer) registerTask(n *graph.Node, resource types.Resource) bool {
	logging.GetLogger().Debugf("Attempting to register task on node %s", n.ID)

	if _, err := n.GetFieldString("Type"); err != nil {
		logging.GetLogger().Infof("Unable to register task type of node unknown %v", n)
		return false
	}

	tid, _ := n.GetFieldString("TID")
	if tid == "" {
		logging.GetLogger().Infof("Unable to register task without node TID %v", n)
		return false
	}

	o.Lock()
	defer o.Unlock()

	if tasks, active := o.activeTasks[n.ID]; active {
		if _, found := tasks[resource.ID()]; found {
			logging.GetLogger().Debugf("A task already exists for %s on node %s", resource.ID(), n.ID)
			return false
		}
	}

	task, err := o.handler.CreateTask(n, resource)
	if err != nil {
		logging.GetLogger().Errorf("Failed to register %s task: %s", o.resourceName, err)
		return false
	}

	active := &activeTask{
		graph:    o.Graph,
		node:     n,
		resource: resource,
		task:     task,
		handler:  o.handler,
	}

	if _, found := o.activeTasks[n.ID]; !found {
		o.activeTasks[n.ID] = make(map[string]*activeTask)
	}
	o.activeTasks[n.ID][resource.ID()] = active

	logging.GetLogger().Debugf("New active task on: %v (%v)", n, resource)
	return true
}

// unregisterTask should be executed under graph lock
func (o *OnDemandServer) unregisterTask(n *graph.Node, resource types.Resource) error {
	o.RLock()
	var active *activeTask
	tasks, isActive := o.activeTasks[n.ID]
	if isActive {
		active, isActive = tasks[resource.ID()]
	}
	o.RUnlock()

	if !isActive {
		return fmt.Errorf("no running task found on node %s", n.ID)
	}

	name, _ := n.GetFieldString("Name")
	logging.GetLogger().Debugf("Attempting to unregister task on node %s (%s)", name, n.ID)

	if err := o.handler.RemoveTask(n, active.resource, active.task); err != nil {
		return err
	}

	o.Lock()
	if tasks, found := o.activeTasks[n.ID]; found {
		delete(tasks, resource.ID())
	}
	if len(o.activeTasks[n.ID]) == 0 {
		delete(o.activeTasks, n.ID)
	}
	o.Unlock()

	return nil
}

// OnStructMessage websocket message, valid message type are Start, Stop
func (o *OnDemandServer) OnStructMessage(c ws.Speaker, msg *ws.StructMessage) {
	var enveloppe ondemand.RawQuery
	if err := json.Unmarshal(msg.Obj, &enveloppe); err != nil {
		logging.GetLogger().Errorf("Unable to decode message %v", msg)
		return
	}

	resource, err := o.handler.DecodeMessage(enveloppe.Resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to decode message %v", o.resourceName)
		return
	}

	query := ondemand.Query{NodeID: enveloppe.NodeID, Resource: resource}

	status := http.StatusBadRequest

	o.Graph.Lock()

	switch msg.Type {
	case "Start":
		n := o.Graph.GetNode(graph.Identifier(query.NodeID))
		if n == nil {
			logging.GetLogger().Errorf("Unknown node %s for new %s", query.NodeID, o.resourceName)
			status = http.StatusNotFound
			break
		}

		status = http.StatusOK
		if _, err := n.GetFieldString(fmt.Sprintf("%s.ID", o.resourceName)); err == nil {
			logging.GetLogger().Debugf("%s already started on node %s", n.ID, o.resourceName)
		} else {
			if ok := o.registerTask(n, resource); !ok {
				status = http.StatusInternalServerError
			}
		}

	case "Stop":
		n := o.Graph.GetNode(graph.Identifier(query.NodeID))
		if n == nil {
			logging.GetLogger().Errorf("Unknown node %s for new %s", query.NodeID, o.resourceName)
			status = http.StatusNotFound
			break
		}

		status = http.StatusOK
		if err := o.unregisterTask(n, resource); err != nil {
			logging.GetLogger().Errorf("Failed to unregister %s on node %s", o.resourceName, n.ID)
			status = http.StatusInternalServerError
		}
	}

	// be sure to unlock before sending message
	o.Graph.Unlock()

	reply := msg.Reply(&query, msg.Type+"Reply", status)
	c.SendMessage(reply)
}

// OnNodeDeleted graph event
func (o *OnDemandServer) OnNodeDeleted(n *graph.Node) {
	o.RLock()
	tasks, found := o.activeTasks[n.ID]
	if found {
		for _, task := range tasks {
			capture := task.resource
			defer func() {
				if err := o.unregisterTask(n, capture); err != nil {
					logging.GetLogger().Errorf("Failed to unregister %s %s on node %s", o.resourceName, capture.ID(), n.ID)
				}
			}()
		}
	}
	o.RUnlock()
}

// Start the task
func (o *OnDemandServer) Start() error {
	o.Graph.AddEventListener(o)
	o.clientPool.AddStructMessageHandler(o, []string{o.wsNamespace})
	return nil
}

// Stop the task
func (o *OnDemandServer) Stop() {
	o.Graph.RemoveEventListener(o)

	o.Graph.Lock()
	for _, tasks := range o.activeTasks {
		for _, active := range tasks {
			o.unregisterTask(active.node, active.resource)
		}
	}
	o.Graph.Unlock()
}

// NewOnDemandServer creates a new Ondemand tasks server based on graph and websocket
func NewOnDemandServer(g *graph.Graph, pool *ws.StructClientPool, handler OnDemandServerHandler) (*OnDemandServer, error) {
	return &OnDemandServer{
		Graph:        g,
		clientPool:   pool,
		activeTasks:  make(map[graph.Identifier]map[string]*activeTask),
		wsNamespace:  ondemand.Namespace + handler.ResourceName(),
		resourceName: handler.ResourceName(),
		handler:      handler,
	}, nil
}
<commit_msg>ondemand: do not enforce TID on nodes<commit_after>/*
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package server

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/ondemand"
	ws "github.com/skydive-project/skydive/websocket"
)

type activeTask struct {
	graph    *graph.Graph
	node     *graph.Node
	resource types.Resource
	task     ondemand.Task
	handler  OnDemandServerHandler
}

// OnDemandServer describes an ondemand task server based on websocket
type OnDemandServer struct {
	common.RWMutex
	graph.DefaultGraphListener
	ws.DefaultSpeakerEventHandler
	Graph        *graph.Graph
	clientPool   *ws.StructClientPool
	activeTasks  map[graph.Identifier]map[string]*activeTask
	wsNamespace  string
	resourceName string
	handler      OnDemandServerHandler
}

// OnDemandServerHandler is the interface to be implemented by ondemand servers
type OnDemandServerHandler interface {
	ResourceName() string
	DecodeMessage(msg json.RawMessage) (types.Resource, error)
	CreateTask(*graph.Node, types.Resource) (interface{}, error)
	RemoveTask(*graph.Node, types.Resource, interface{}) error
}

func (o *OnDemandServer) registerTask(n *graph.Node, resource types.Resource) bool {
	logging.GetLogger().Debugf("Attempting to register %s %s on node %s", o.resourceName, resource.ID(), n.ID)

	if _, err := n.GetFieldString("Type"); err != nil {
		logging.GetLogger().Infof("Unable to register task type of node unknown %v", n)
		return false
	}

	o.Lock()
	defer o.Unlock()

	if tasks, active := o.activeTasks[n.ID]; active {
		if _, found := tasks[resource.ID()]; found {
			logging.GetLogger().Debugf("A task already exists for %s on node %s", resource.ID(), n.ID)
			return false
		}
	}

	task, err := o.handler.CreateTask(n, resource)
	if err != nil {
		logging.GetLogger().Errorf("Failed to register %s task: %s", o.resourceName, err)
		return false
	}

	active := &activeTask{
		graph:    o.Graph,
		node:     n,
		resource: resource,
		task:     task,
		handler:  o.handler,
	}

	if _, found := o.activeTasks[n.ID]; !found {
		o.activeTasks[n.ID] = make(map[string]*activeTask)
	}
	o.activeTasks[n.ID][resource.ID()] = active

	logging.GetLogger().Debugf("New active task on: %v (%v)", n, resource)
	return true
}

// unregisterTask should be executed under graph lock
func (o *OnDemandServer) unregisterTask(n *graph.Node, resource types.Resource) error {
	o.RLock()
	var active *activeTask
	tasks, isActive := o.activeTasks[n.ID]
	if isActive {
		active, isActive = tasks[resource.ID()]
	}
	o.RUnlock()

	if !isActive {
		return fmt.Errorf("no running task found on node %s", n.ID)
	}

	name, _ := n.GetFieldString("Name")
	logging.GetLogger().Debugf("Attempting to unregister task on node %s (%s)", name, n.ID)

	if err := o.handler.RemoveTask(n, active.resource, active.task); err != nil {
		return err
	}

	o.Lock()
	if tasks, found := o.activeTasks[n.ID]; found {
		delete(tasks, resource.ID())
	}
	if len(o.activeTasks[n.ID]) == 0 {
		delete(o.activeTasks, n.ID)
	}
	o.Unlock()

	return nil
}

// OnStructMessage websocket message, valid message type are Start, Stop
func (o *OnDemandServer) OnStructMessage(c ws.Speaker, msg *ws.StructMessage) {
	var enveloppe ondemand.RawQuery
	if err := json.Unmarshal(msg.Obj, &enveloppe); err != nil {
		logging.GetLogger().Errorf("Unable to decode message %v", msg)
		return
	}

	resource, err := o.handler.DecodeMessage(enveloppe.Resource)
	if err != nil {
		logging.GetLogger().Errorf("Unable to decode message %v", o.resourceName)
		return
	}

	query := ondemand.Query{NodeID: enveloppe.NodeID, Resource: resource}

	status := http.StatusBadRequest

	o.Graph.Lock()

	switch msg.Type {
	case "Start":
		n := o.Graph.GetNode(graph.Identifier(query.NodeID))
		if n == nil {
			logging.GetLogger().Errorf("Unknown node %s for new %s", query.NodeID, o.resourceName)
			status = http.StatusNotFound
			break
		}

		status = http.StatusOK
		if _, err := n.GetFieldString(fmt.Sprintf("%s.ID", o.resourceName)); err == nil {
			logging.GetLogger().Debugf("%s already started on node %s", n.ID, o.resourceName)
		} else {
			if ok := o.registerTask(n, resource); !ok {
				status = http.StatusInternalServerError
			}
		}

	case "Stop":
		n := o.Graph.GetNode(graph.Identifier(query.NodeID))
		if n == nil {
			logging.GetLogger().Errorf("Unknown node %s for new %s", query.NodeID, o.resourceName)
			status = http.StatusNotFound
			break
		}

		status = http.StatusOK
		if err := o.unregisterTask(n, resource); err != nil {
			logging.GetLogger().Errorf("Failed to unregister %s on node %s", o.resourceName, n.ID)
			status = http.StatusInternalServerError
		}
	}

	// be sure to unlock before sending message
	o.Graph.Unlock()

	reply := msg.Reply(&query, msg.Type+"Reply", status)
	c.SendMessage(reply)
}

// OnNodeDeleted graph event
func (o *OnDemandServer) OnNodeDeleted(n *graph.Node) {
	o.RLock()
	tasks, found := o.activeTasks[n.ID]
	if found {
		for _, task := range tasks {
			capture := task.resource
			defer func() {
				if err := o.unregisterTask(n, capture); err != nil {
					logging.GetLogger().Errorf("Failed to unregister %s %s on node %s", o.resourceName, capture.ID(), n.ID)
				}
			}()
		}
	}
	o.RUnlock()
}

// Start the task
func (o *OnDemandServer) Start() error {
	o.Graph.AddEventListener(o)
	o.clientPool.AddStructMessageHandler(o, []string{o.wsNamespace})
	return nil
}

// Stop the task
func (o *OnDemandServer) Stop() {
	o.Graph.RemoveEventListener(o)

	o.Graph.Lock()
	for _, tasks := range o.activeTasks {
		for _, active := range tasks {
			o.unregisterTask(active.node, active.resource)
		}
	}
	o.Graph.Unlock()
}

// NewOnDemandServer creates a new Ondemand tasks server based on graph and websocket
func NewOnDemandServer(g *graph.Graph, pool *ws.StructClientPool, handler OnDemandServerHandler) (*OnDemandServer, error) {
	return &OnDemandServer{
		Graph:        g,
		clientPool:   pool,
		activeTasks:  make(map[graph.Identifier]map[string]*activeTask),
		wsNamespace:  ondemand.Namespace + handler.ResourceName(),
		resourceName: handler.ResourceName(),
		handler:      handler,
	}, nil
}
<|endoftext|>


<commit_before>package agent

import (
	"fmt"
	"net"
	"os"
	"time"
)

// HTTPServer is used to wrap an Agent and expose various API's
// in a RESTful manner
type HTTPServer struct {
	agent *Agent
	mux *http.ServeMux
	listener net.Listener
	logger *log.Logger
}

// NewHTTPServer starts a new HTTP server to provide an interface to
// the agent.
func NewHTTPServer(agent *Agent, enableDebug bool, logOutput io.Writer, bind string) (*HTTPServer, error) {
	// Create the mux.
	mux := http.NewServeMux()

	// Create listener
	list, err := net.Listen("tcp", bind)
	if err != nil {
		return nil, err
	}

	// Create the server
	srv := &HTTPServer{
		agent:    agent,
		mux:      mux,
		listener: list,
		logger:   log.New(logOutput, "", log.LstdFlags),
	}
	srv.registerHandlers(enableDebug)

	// Start the server
	go http.Serve(list, mux)
	return srv, nil
}

// Shutdown is used to shutdown the HTTP server
func (s *HTTPServer) Shutdown() {
	s.listener.Close()
}

func (s *HTTPServer) Handlers (enableDebug, bool) {
	s.mux.HandleFunc("/", s.Index)

	s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembers))

	s.mux.HandleFunc("/v1/ipxe/nodes"), s.wrap(s.Nodes))
	s.mux.HandleFunc("/v1/ipxe/{id}", s.wrap(s.NodeIpxe))

	if enableDebug {
		s.mux.HandleFunc("/debug/pprof/", pprof.Index)
		s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
		s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
		s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	}

	// Enable the UI + special endpoints
	if s.uiDir != "" {
		// Static file serving done from /ui/
		s.mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.uiDir))))

		// API's are under /internal/ui/ to avoid conflict
		s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes))
		s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo))
		s.mux.HandleFunc("/v1/internal/ui/services", s.wrap(s.UIServices))
	}
}

// wrap is used to wrap functions to make them more convenient
func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
	f := func(resp http.ResponseWriter, req *http.Request) {
		// Invoke the handler
		start := time.Now()
		defer func() {
			s.logger.Printf("[DEBUG] http: Request %v (%v)", req.URL, time.Now().Sub(start))
		}()
		obj, err := handler(resp, req)

		// Check for an error
	HAS_ERR:
		if err != nil {
			s.logger.Printf("[ERR] http: Request %v, error: %v", req.URL, err)
			resp.WriteHeader(500)
			resp.Write([]byte(err.Error()))
			return
		}

		// Write out the JSON object
		if obj != nil {
			var buf bytes.Buffer
			enc := json.NewEncoder(&buf)
			if err = enc.Encode(obj); err != nil {
				goto HAS_ERR
			}
			resp.Header().Set("Content-Type", "application/json")
			resp.Write(buf.Bytes())
		}
	}
	return f
}<commit_msg>add functions for http<commit_after>package agent

import (
	"fmt"
	"net"
	"os"
	"time"
)

// HTTPServer is used to wrap an Agent and expose various API's
// in a RESTful manner
type HTTPServer struct {
	agent *Agent
	mux *http.ServeMux
	listener net.Listener
	logger *log.Logger
}

// NewHTTPServer starts a new HTTP server to provide an interface to
// the agent.
func NewHTTPServer(agent *Agent, enableDebug bool, logOutput io.Writer, bind string) (*HTTPServer, error) {
	// Create the mux.
	mux := http.NewServeMux()

	// Create listener
	list, err := net.Listen("tcp", bind)
	if err != nil {
		return nil, err
	}

	// Create the server
	srv := &HTTPServer{
		agent:    agent,
		mux:      mux,
		listener: list,
		logger:   log.New(logOutput, "", log.LstdFlags),
	}
	srv.registerHandlers(enableDebug)

	// Start the server
	go http.Serve(list, mux)
	return srv, nil
}

// Shutdown is used to shutdown the HTTP server
func (s *HTTPServer) Shutdown() {
	s.listener.Close()
}

func (s *HTTPServer) Handlers (enableDebug, bool) {
	s.mux.HandleFunc("/", s.Index)

	s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembers))

	s.mux.HandleFunc("/v1/ipxe/nodes"), s.wrap(s.Nodes))
	s.mux.HandleFunc("/v1/ipxe/{id}", s.wrap(s.NodeIpxe))

	if enableDebug {
		s.mux.HandleFunc("/debug/pprof/", pprof.Index)
		s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
		s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
		s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	}

	// Enable the UI + special endpoints
	if s.uiDir != "" {
		// Static file serving done from /ui/
		s.mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.uiDir))))

		// API's are under /internal/ui/ to avoid conflict
		s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes))
		s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo))
		s.mux.HandleFunc("/v1/internal/ui/services", s.wrap(s.UIServices))
	}
}

// wrap is used to wrap functions to make them more convenient
func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
	f := func(resp http.ResponseWriter, req *http.Request) {
		// Invoke the handler
		start := time.Now()
		defer func() {
			s.logger.Printf("[DEBUG] http: Request %v (%v)", req.URL, time.Now().Sub(start))
		}()
		obj, err := handler(resp, req)

		// Check for an error
	HAS_ERR:
		if err != nil {
			s.logger.Printf("[ERR] http: Request %v, error: %v", req.URL, err)
			resp.WriteHeader(500)
			resp.Write([]byte(err.Error()))
			return
		}

		// Write out the JSON object
		if obj != nil {
			var buf bytes.Buffer
			enc := json.NewEncoder(&buf)
			if err = enc.Encode(obj); err != nil {
				goto HAS_ERR
			}
			resp.Header().Set("Content-Type", "application/json")
			resp.Write(buf.Bytes())
		}
	}
	return f
}

// Renders a simple index page
func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) {
	// Check if this is a non-index path
	if req.URL.Path != "/" {
		resp.WriteHeader(404)
		return
	}

	// Check if we have no UI configured
	if s.uiDir == "" {
		resp.Write([]byte("Consul Agent"))
		return
	}

	// Redirect to the UI endpoint
	http.Redirect(resp, req, "/ui/", 301)
}

// decodeBody is used to decode a JSON request body
func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error) error {
	var raw interface{}
	dec := json.NewDecoder(req.Body)
	if err := dec.Decode(&raw); err != nil {
		return err
	}

	// Invoke the callback prior to decode
	if cb != nil {
		if err := cb(raw); err != nil {
			return err
		}
	}
	return mapstructure.Decode(raw, out)
}

// setIndex is used to set the index response header
func setIndex(resp http.ResponseWriter, index uint64) {
	resp.Header().Add("X-Consul-Index", strconv.FormatUint(index, 10))
}<|endoftext|>


<commit_before>package cli

var all_templates = map[string]string{
	"debug":          default_debug_template,
	"fields":         default_debug_template,
	"editmeta":       default_debug_template,
	"transmeta":      default_debug_template,
	"createmeta":     default_debug_template,
	"issuelinktypes": default_debug_template,
	"list":           default_list_template,
	"view":           default_view_template,
	"edit":           default_edit_template,
	"transitions":    default_transitions_template,
	"issuetypes":     default_issuetypes_template,
	"create":         default_create_template,
	"comment":        default_comment_template,
	"transition":     default_transition_template,
}

const default_debug_template = "{{ . | toJson}}\n"

const default_list_template = "{{ range .issues }}{{ .key | append \":\" | printf \"%-12s\"}} {{ .fields.summary }}\n{{ end }}"

const default_view_template = `issue: {{ .key }}
status: {{ .fields.status.name }}
summary: {{ .fields.summary }}
project: {{ .fields.project.key }}
components: {{ range .fields.components }}{{ .name }} {{end}}
issuetype: {{ .fields.issuetype.name }}
assignee: {{ if .fields.assignee }}{{ .fields.assignee.name }}{{end}}
reporter: {{ .fields.reporter.name }}
watchers: {{ range .fields.customfield_10110 }}{{ .name }} {{end}}
blockers: {{ range .fields.issuelinks }}{{if .outwardIssue}}{{ .outwardIssue.key }}[{{.outwardIssue.fields.status.name}}]{{end}}{{end}}
depends: {{ range .fields.issuelinks }}{{if .inwardIssue}}{{ .inwardIssue.key }}[{{.inwardIssue.fields.status.name}}]{{end}}{{end}}
priority: {{ .fields.priority.name }}
description: |
  {{ if .fields.description }}{{.fields.description | indent 2 }}{{end}}

comments:
{{ range .fields.comment.comments }}  - | # {{.author.name}} at {{.created}}
    {{ .body | indent 4}}
{{end}}
`
const default_edit_template = `update:
  comment:
    - add:
        body: |
          {{ or .overrides.comment "" | indent 10 }}
fields:
  summary: {{ or .overrides.summary .fields.summary }}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
    - name: {{.}}{{end}}{{else}}{{ range .fields.components }}
    - name: {{ .name }}{{end}}{{end}}
  assignee:
    name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
  reporter:
    name: {{ or .overrides.reporter .fields.reporter.name }}
  # watchers
  customfield_10110: {{ range .fields.customfield_10110 }}
    - name: {{ .name }}{{end}}{{if .overrides.watcher}}
    - name: {{ .overrides.watcher}}{{end}}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority .fields.priority.name }}
  description: |
    {{ or .overrides.description (or .fields.description "") | indent 4 }}
`
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
{{end}}`

const default_issuetypes_template = `{{ range .projects }}{{ range .issuetypes }}{{color "+bh"}}{{.name | append ":" | printf "%-13s" }}{{color "reset"}} {{.description}}
{{end}}{{end}}`

const default_create_template = `fields:
  project:
    key: {{ .overrides.project }}
  issuetype:
    name: {{ .overrides.issuetype }}
  summary: {{ or .overrides.summary "" }}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority "unassigned" }}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
    - name: {{ . }}{{end}}
  description: |
    {{ or .overrides.description "" | indent 4 }}
  assignee:
    name: {{ or .overrides.assignee .overrides.user}}
  reporter:
    name: {{ or .overrides.reporter .overrides.user }}
  # watchers
  customfield_10110:
    - name:
`

const default_comment_template = `body: |
  {{ or .overrides.comment | indent 2 }}
`

const default_transition_template = `update:
  comment:
    - add:
        body: |
          {{ or .overrides.comment "" | indent 10 }}
fields:{{if .meta.fields.assignee}}
  assignee:
    name: {{if .overrides.assignee}}{{.overrides.assignee}}{{else}}{{if .fields.assignee}}{{.fields.assignee.name}}{{end}}{{end}}{{end}}{{if .meta.fields.components}}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
    - name: {{.}}{{end}}{{else}}{{ range .fields.components }}
    - name: {{ .name }}{{end}}{{end}}{{end}}{{if .meta.fields.description}}
  description: {{or .overrides.description .fields.description }}{{end}}{{if .meta.fields.fixVersions}}{{if .meta.fields.fixVersions.allowedValues}}
  fixVersions: # {{ range .meta.fields.fixVersions.allowedValues }}{{.name}}, {{end}}{{if .overrides.fixVersions}}{{ range (split "," .overrides.fixVersions)}}
    - name: {{.}}{{end}}{{else}}{{range .fields.fixVersions}}
    - name: {{.}}{{end}}{{end}}{{end}}{{end}}{{if .meta.fields.issuetype}}
  issuetype: # {{ range .meta.fields.issuetype.allowedValues }}{{.name}}, {{end}}
    name: {{if .overrides.issuetype}}{{.overrides.issuetype}}{{else}}{{if .fields.issuetype}}{{.fields.issuetype.name}}{{end}}{{end}}{{end}}{{if .meta.fields.labels}}
  labels: {{range .fields.labels}}
    - {{.}}{{end}}{{if .overrides.labels}}{{range (split "," .overrides.labels)}}
    - {{.}}{{end}}{{end}}{{end}}{{if .meta.fields.priority}}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority "unassigned" }}{{end}}{{if .meta.fields.reporter}}
  reporter:
    name: {{if .overrides.reporter}}{{.overrides.reporter}}{{else}}{{if .fields.reporter}}{{.fields.reporter.name}}{{end}}{{end}}{{end}}{{if .meta.fields.resolution}}
  resolution: # {{ range .meta.fields.resolution.allowedValues }}{{.name}}, {{end}}
    name: {{if .overrides.resolution}}{{.overrides.resolution}}{{else if .fields.resolution}}{{.fields.resolution.name}}{{else}}Fixed{{end}}{{end}}{{if .meta.fields.summary}}
  summary: {{or .overrides.summary .fields.summary}}{{end}}{{if .meta.fields.versions.allowedValues}}
  versions: # {{ range .meta.fields.versions.allowedValues }}{{.name}}, {{end}}{{if .overrides.versions}}{{ range (split "," .overrides.versions)}}
    - name: {{.}}{{end}}{{else}}{{range .fields.versions}}
    - name: {{.}}{{end}}{{end}}{{end}}
transition:
  id: {{ .transition.id }}
  name: {{ .transition.name }}
`
<commit_msg>added created field to "view" template<commit_after>package cli

var all_templates = map[string]string{
	"debug":          default_debug_template,
	"fields":         default_debug_template,
	"editmeta":       default_debug_template,
	"transmeta":      default_debug_template,
	"createmeta":     default_debug_template,
	"issuelinktypes": default_debug_template,
	"list":           default_list_template,
	"view":           default_view_template,
	"edit":           default_edit_template,
	"transitions":    default_transitions_template,
	"issuetypes":     default_issuetypes_template,
	"create":         default_create_template,
	"comment":        default_comment_template,
	"transition":     default_transition_template,
}

const default_debug_template = "{{ . | toJson}}\n"

const default_list_template = "{{ range .issues }}{{ .key | append \":\" | printf \"%-12s\"}} {{ .fields.summary }}\n{{ end }}"

const default_view_template = `issue: {{ .key }}
created: {{ .fields.created }}
status: {{ .fields.status.name }}
summary: {{ .fields.summary }}
project: {{ .fields.project.key }}
components: {{ range .fields.components }}{{ .name }} {{end}}
issuetype: {{ .fields.issuetype.name }}
assignee: {{ if .fields.assignee }}{{ .fields.assignee.name }}{{end}}
reporter: {{ .fields.reporter.name }}
watchers: {{ range .fields.customfield_10110 }}{{ .name }} {{end}}
blockers: {{ range .fields.issuelinks }}{{if .outwardIssue}}{{ .outwardIssue.key }}[{{.outwardIssue.fields.status.name}}]{{end}}{{end}}
depends: {{ range .fields.issuelinks }}{{if .inwardIssue}}{{ .inwardIssue.key }}[{{.inwardIssue.fields.status.name}}]{{end}}{{end}}
priority: {{ .fields.priority.name }}
description: |
  {{ if .fields.description }}{{.fields.description | indent 2 }}{{end}}

comments:
{{ range .fields.comment.comments }}  - | # {{.author.name}} at {{.created}}
    {{ .body | indent 4}}
{{end}}
`
const default_edit_template = `update:
  comment:
    - add:
        body: |
          {{ or .overrides.comment "" | indent 10 }}
fields:
  summary: {{ or .overrides.summary .fields.summary }}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
    - name: {{.}}{{end}}{{else}}{{ range .fields.components }}
    - name: {{ .name }}{{end}}{{end}}
  assignee:
    name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
  reporter:
    name: {{ or .overrides.reporter .fields.reporter.name }}
  # watchers
  customfield_10110: {{ range .fields.customfield_10110 }}
    - name: {{ .name }}{{end}}{{if .overrides.watcher}}
    - name: {{ .overrides.watcher}}{{end}}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority .fields.priority.name }}
  description: |
    {{ or .overrides.description (or .fields.description "") | indent 4 }}
`
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
{{end}}`

const default_issuetypes_template = `{{ range .projects }}{{ range .issuetypes }}{{color "+bh"}}{{.name | append ":" | printf "%-13s" }}{{color "reset"}} {{.description}}
{{end}}{{end}}`

const default_create_template = `fields:
  project:
    key: {{ .overrides.project }}
  issuetype:
    name: {{ .overrides.issuetype }}
  summary: {{ or .overrides.summary "" }}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority "unassigned" }}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
    - name: {{ . }}{{end}}
  description: |
    {{ or .overrides.description "" | indent 4 }}
  assignee:
    name: {{ or .overrides.assignee .overrides.user}}
  reporter:
    name: {{ or .overrides.reporter .overrides.user }}
  # watchers
  customfield_10110:
    - name:
`

const default_comment_template = `body: |
  {{ or .overrides.comment | indent 2 }}
`

const default_transition_template = `update:
  comment:
    - add:
        body: |
          {{ or .overrides.comment "" | indent 10 }}
fields:{{if .meta.fields.assignee}}
  assignee:
    name: {{if .overrides.assignee}}{{.overrides.assignee}}{{else}}{{if .fields.assignee}}{{.fields.assignee.name}}{{end}}{{end}}{{end}}{{if .meta.fields.components}}
  components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
    - name: {{.}}{{end}}{{else}}{{ range .fields.components }}
    - name: {{ .name }}{{end}}{{end}}{{end}}{{if .meta.fields.description}}
  description: {{or .overrides.description .fields.description }}{{end}}{{if .meta.fields.fixVersions}}{{if .meta.fields.fixVersions.allowedValues}}
  fixVersions: # {{ range .meta.fields.fixVersions.allowedValues }}{{.name}}, {{end}}{{if .overrides.fixVersions}}{{ range (split "," .overrides.fixVersions)}}
    - name: {{.}}{{end}}{{else}}{{range .fields.fixVersions}}
    - name: {{.}}{{end}}{{end}}{{end}}{{end}}{{if .meta.fields.issuetype}}
  issuetype: # {{ range .meta.fields.issuetype.allowedValues }}{{.name}}, {{end}}
    name: {{if .overrides.issuetype}}{{.overrides.issuetype}}{{else}}{{if .fields.issuetype}}{{.fields.issuetype.name}}{{end}}{{end}}{{end}}{{if .meta.fields.labels}}
  labels: {{range .fields.labels}}
    - {{.}}{{end}}{{if .overrides.labels}}{{range (split "," .overrides.labels)}}
    - {{.}}{{end}}{{end}}{{end}}{{if .meta.fields.priority}}
  priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
    name: {{ or .overrides.priority "unassigned" }}{{end}}{{if .meta.fields.reporter}}
  reporter:
    name: {{if .overrides.reporter}}{{.overrides.reporter}}{{else}}{{if .fields.reporter}}{{.fields.reporter.name}}{{end}}{{end}}{{end}}{{if .meta.fields.resolution}}
  resolution: # {{ range .meta.fields.resolution.allowedValues }}{{.name}}, {{end}}
    name: {{if .overrides.resolution}}{{.overrides.resolution}}{{else if .fields.resolution}}{{.fields.resolution.name}}{{else}}Fixed{{end}}{{end}}{{if .meta.fields.summary}}
  summary: {{or .overrides.summary .fields.summary}}{{end}}{{if .meta.fields.versions.allowedValues}}
  versions: # {{ range .meta.fields.versions.allowedValues }}{{.name}}, {{end}}{{if .overrides.versions}}{{ range (split "," .overrides.versions)}}
    - name: {{.}}{{end}}{{else}}{{range .fields.versions}}
    - name: {{.}}{{end}}{{end}}{{end}}
transition:
  id: {{ .transition.id }}
  name: {{ .transition.name }}
`
<|endoftext|>


<commit_before>// speculator allows you to preview pull requests to the matrix.org specification.
// It serves the following HTTP endpoints:
//  - / lists open pull requests
//  - /spec/123 which renders the spec as html at pull request 123.
//  - /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
//  - /diff/html/123 which gives a diff of the spec's HTML at pull request 123.
// It is currently woefully inefficient, and there is a lot of low hanging fruit for improvement.
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type PullRequest struct {
	Number  int
	Base    Commit
	Head    Commit
	Title   string
	User    User
	HTMLURL string `json:"html_url"`
}

type Commit struct {
	SHA  string
	Repo RequestRepo
}

type RequestRepo struct {
	CloneURL string `json:"clone_url"`
}

type User struct {
	Login   string
	HTMLURL string `json:"html_url"`
}

var (
	port           = flag.Int("port", 9000, "Port on which to listen for HTTP")
	allowedMembers map[string]bool
)

func (u *User) IsTrusted() bool {
	return allowedMembers[u.Login]
}

const (
	pullsPrefix       = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
	matrixDocCloneURL = "https://github.com/matrix-org/matrix-doc.git"
)

func gitClone(url string, shared bool) (string, error) {
	directory := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
	cmd := exec.Command("git", "clone", url, directory)
	if shared {
		cmd.Args = append(cmd.Args, "--shared")
	}

	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("error cloning repo: %v", err)
	}
	return directory, nil
}

func gitCheckout(path, sha string) error {
	return runGitCommand(path, []string{"checkout", sha})
}

func gitFetch(path string) error {
	return runGitCommand(path, []string{"fetch"})
}

func runGitCommand(path string, args []string) error {
	cmd := exec.Command("git", args...)
	cmd.Dir = path
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("error running %q: %v", strings.Join(cmd.Args, " "), err)
	}
	return nil
}

func lookupPullRequest(url url.URL, pathPrefix string) (*PullRequest, error) {
	if !strings.HasPrefix(url.Path, pathPrefix+"/") {
		return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix)
	}
	prNumber := url.Path[len(pathPrefix)+1:]
	if strings.Contains(prNumber, "/") {
		return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix)
	}

	resp, err := http.Get(fmt.Sprintf("%s/%s", pullsPrefix, prNumber))
	defer resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("error getting pulls: %v", err)
	}
	dec := json.NewDecoder(resp.Body)
	var pr PullRequest
	if err := dec.Decode(&pr); err != nil {
		return nil, fmt.Errorf("error decoding pulls: %v", err)
	}
	return &pr, nil
}

func generate(dir string) error {
	cmd := exec.Command("python", "gendoc.py", "--nodelete")
	cmd.Dir = path.Join(dir, "scripts")
	var b bytes.Buffer
	cmd.Stderr = &b
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
	}
	return nil
}

func writeError(w http.ResponseWriter, code int, err error) {
	w.WriteHeader(code)
	io.WriteString(w, fmt.Sprintf("%v\n", err))
}

type server struct {
	matrixDocCloneURL string
}

// generateAt generates spec from repo at sha.
// Returns the path where the generation was done.
func (s *server) generateAt(sha string) (dst string, err error) {
	err = gitFetch(s.matrixDocCloneURL)
	if err != nil {
		return
	}
	dst, err = gitClone(s.matrixDocCloneURL, true)
	if err != nil {
		return
	}

	if err = gitCheckout(dst, sha); err != nil {
		return
	}

	err = generate(dst)
	return
}

func (s *server) getSHAOf(ref string) (string, error) {
	cmd := exec.Command("git", "rev-list", ref, "-n1")
	cmd.Dir = path.Join(s.matrixDocCloneURL)
	var b bytes.Buffer
	cmd.Stdout = &b
	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
	}
	return strings.TrimSpace(b.String()), nil
}

func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) {
	var sha string

	if strings.ToLower(req.URL.Path) == "/spec/head" {
		originHead, err := s.getSHAOf("origin/master")
		if err != nil {
			writeError(w, 500, err)
			return
		}
		sha = originHead
	} else {
		pr, err := lookupPullRequest(*req.URL, "/spec")
		if err != nil {
			writeError(w, 400, err)
			return
		}

		// We're going to run whatever Python is specified in the pull request, which
		// may do bad things, so only trust people we trust.
		if err := checkAuth(pr); err != nil {
			writeError(w, 403, err)
			return
		}
		sha = pr.Head.SHA
	}

	dst, err := s.generateAt(sha)
	defer os.RemoveAll(dst)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	b, err := ioutil.ReadFile(path.Join(dst, "scripts/gen/specification.html"))
	if err != nil {
		writeError(w, 500, fmt.Errorf("Error reading spec: %v", err))
		return
	}
	w.Write(b)
}

func checkAuth(pr *PullRequest) error {
	if !pr.User.IsTrusted() {
		return fmt.Errorf("%q is not a trusted pull requester", pr.User.Login)
	}
	return nil
}

func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) {
	pr, err := lookupPullRequest(*req.URL, "/diff/rst")
	if err != nil {
		writeError(w, 400, err)
		return
	}

	// We're going to run whatever Python is specified in the pull request, which
	// may do bad things, so only trust people we trust.
	if err := checkAuth(pr); err != nil {
		writeError(w, 403, err)
		return
	}

	base, err := s.generateAt(pr.Base.SHA)
	defer os.RemoveAll(base)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	head, err := s.generateAt(pr.Head.SHA)
	defer os.RemoveAll(head)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	diffCmd := exec.Command("diff", "-u", path.Join(base, "scripts", "tmp", "full_spec.rst"), path.Join(head, "scripts", "tmp", "full_spec.rst"))
	var diff bytes.Buffer
	diffCmd.Stdout = &diff
	if err := ignoreExitCodeOne(diffCmd.Run()); err != nil {
		writeError(w, 500, fmt.Errorf("error running diff: %v", err))
		return
	}
	w.Write(diff.Bytes())
}

func (s *server) serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
	pr, err := lookupPullRequest(*req.URL, "/diff/html")
	if err != nil {
		writeError(w, 400, err)
		return
	}

	// We're going to run whatever Python is specified in the pull request, which
	// may do bad things, so only trust people we trust.
	if err := checkAuth(pr); err != nil {
		writeError(w, 403, err)
		return
	}

	base, err := s.generateAt(pr.Base.SHA)
	defer os.RemoveAll(base)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	head, err := s.generateAt(pr.Head.SHA)
	defer os.RemoveAll(head)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	htmlDiffer, err := findHTMLDiffer()
	if err != nil {
		writeError(w, 500, fmt.Errorf("could not find HTML differ"))
		return
	}

	cmd := exec.Command(htmlDiffer, path.Join(base, "scripts", "gen", "specification.html"), path.Join(head, "scripts", "gen", "specification.html"))
	var b bytes.Buffer
	cmd.Stdout = &b
	if err := cmd.Run(); err != nil {
		writeError(w, 500, fmt.Errorf("error running HTML differ: %v", err))
		return
	}
	w.Write(b.Bytes())
}

func findHTMLDiffer() (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	differ := path.Join(wd, "htmldiff.pl")
	if _, err := os.Stat(differ); err == nil {
		return differ, nil
	}
	return "", fmt.Errorf("unable to find htmldiff.pl")
}

func listPulls(w http.ResponseWriter, req *http.Request) {
	resp, err := http.Get(pullsPrefix)
	if err != nil {
		writeError(w, 500, err)
		return
	}
	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	var pulls []PullRequest
	if err := dec.Decode(&pulls); err != nil {
		writeError(w, 500, err)
		return
	}
	if len(pulls) == 0 {
		io.WriteString(w, "No pull requests found")
		return
	}
	s := "<body><ul>"
	for _, pull := range pulls {
		s += fmt.Sprintf(`<li>%d: <a href="%s">%s</a>: <a href="%s">%s</a>: <a href="spec/%d">spec</a> <a href="diff/html/%d">spec diff</a> <a href="diff/rst/%d">rst diff</a></li>`,
			pull.Number, pull.User.HTMLURL, pull.User.Login, pull.HTMLURL, pull.Title, pull.Number, pull.Number, pull.Number)
	}
	s += `</ul><div><a href="spec/head">View the spec at head</a></div></body>`
	io.WriteString(w, s)
}

func ignoreExitCodeOne(err error) error {
	if err == nil {
		return err
	}

	if exiterr, ok := err.(*exec.ExitError); ok {
		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
			if status.ExitStatus() == 1 {
				return nil
			}
		}
	}
	return err
}

func main() {
	flag.Parse()
	// It would be great to read this from github, but there's no convenient way to do so.
	// Most of these memberships are "private", so would require some kind of auth.
	allowedMembers = map[string]bool{
		"dbkr":          true,
		"erikjohnston":  true,
		"illicitonion":  true,
		"Kegsay":        true,
		"NegativeMjark": true,
	}
	rand.Seed(time.Now().Unix())
	masterCloneDir, err := gitClone(matrixDocCloneURL, false)
	if err != nil {
		log.Fatal(err)
	}
	s := server{masterCloneDir}
	http.HandleFunc("/spec/", s.serveSpec)
	http.HandleFunc("/diff/rst/", s.serveRSTDiff)
	http.HandleFunc("/diff/html/", s.serveHTMLDiff)
	http.HandleFunc("/healthz", serveText("ok"))
	http.HandleFunc("/", listPulls)

	fmt.Printf("Listening on port %d\n", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

func serveText(s string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, s)
	}
}
<commit_msg>Add richvdh to list of trusted pushers for spectulator<commit_after>// speculator allows you to preview pull requests to the matrix.org specification.
// It serves the following HTTP endpoints:
//  - / lists open pull requests
//  - /spec/123 which renders the spec as html at pull request 123.
//  - /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
//  - /diff/html/123 which gives a diff of the spec's HTML at pull request 123.
// It is currently woefully inefficient, and there is a lot of low hanging fruit for improvement.
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type PullRequest struct {
	Number  int
	Base    Commit
	Head    Commit
	Title   string
	User    User
	HTMLURL string `json:"html_url"`
}

type Commit struct {
	SHA  string
	Repo RequestRepo
}

type RequestRepo struct {
	CloneURL string `json:"clone_url"`
}

type User struct {
	Login   string
	HTMLURL string `json:"html_url"`
}

var (
	port           = flag.Int("port", 9000, "Port on which to listen for HTTP")
	allowedMembers map[string]bool
)

func (u *User) IsTrusted() bool {
	return allowedMembers[u.Login]
}

const (
	pullsPrefix       = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
	matrixDocCloneURL = "https://github.com/matrix-org/matrix-doc.git"
)

func gitClone(url string, shared bool) (string, error) {
	directory := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
	cmd := exec.Command("git", "clone", url, directory)
	if shared {
		cmd.Args = append(cmd.Args, "--shared")
	}

	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("error cloning repo: %v", err)
	}
	return directory, nil
}

func gitCheckout(path, sha string) error {
	return runGitCommand(path, []string{"checkout", sha})
}

func gitFetch(path string) error {
	return runGitCommand(path, []string{"fetch"})
}

func runGitCommand(path string, args []string) error {
	cmd := exec.Command("git", args...)
	cmd.Dir = path
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("error running %q: %v", strings.Join(cmd.Args, " "), err)
	}
	return nil
}

func lookupPullRequest(url url.URL, pathPrefix string) (*PullRequest, error) {
	if !strings.HasPrefix(url.Path, pathPrefix+"/") {
		return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix)
	}
	prNumber := url.Path[len(pathPrefix)+1:]
	if strings.Contains(prNumber, "/") {
		return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix)
	}

	resp, err := http.Get(fmt.Sprintf("%s/%s", pullsPrefix, prNumber))
	defer resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("error getting pulls: %v", err)
	}
	dec := json.NewDecoder(resp.Body)
	var pr PullRequest
	if err := dec.Decode(&pr); err != nil {
		return nil, fmt.Errorf("error decoding pulls: %v", err)
	}
	return &pr, nil
}

func generate(dir string) error {
	cmd := exec.Command("python", "gendoc.py", "--nodelete")
	cmd.Dir = path.Join(dir, "scripts")
	var b bytes.Buffer
	cmd.Stderr = &b
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
	}
	return nil
}

func writeError(w http.ResponseWriter, code int, err error) {
	w.WriteHeader(code)
	io.WriteString(w, fmt.Sprintf("%v\n", err))
}

type server struct {
	matrixDocCloneURL string
}

// generateAt generates spec from repo at sha.
// Returns the path where the generation was done.
func (s *server) generateAt(sha string) (dst string, err error) {
	err = gitFetch(s.matrixDocCloneURL)
	if err != nil {
		return
	}
	dst, err = gitClone(s.matrixDocCloneURL, true)
	if err != nil {
		return
	}

	if err = gitCheckout(dst, sha); err != nil {
		return
	}

	err = generate(dst)
	return
}

func (s *server) getSHAOf(ref string) (string, error) {
	cmd := exec.Command("git", "rev-list", ref, "-n1")
	cmd.Dir = path.Join(s.matrixDocCloneURL)
	var b bytes.Buffer
	cmd.Stdout = &b
	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
	}
	return strings.TrimSpace(b.String()), nil
}

func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) {
	var sha string

	if strings.ToLower(req.URL.Path) == "/spec/head" {
		originHead, err := s.getSHAOf("origin/master")
		if err != nil {
			writeError(w, 500, err)
			return
		}
		sha = originHead
	} else {
		pr, err := lookupPullRequest(*req.URL, "/spec")
		if err != nil {
			writeError(w, 400, err)
			return
		}

		// We're going to run whatever Python is specified in the pull request, which
		// may do bad things, so only trust people we trust.
		if err := checkAuth(pr); err != nil {
			writeError(w, 403, err)
			return
		}
		sha = pr.Head.SHA
	}

	dst, err := s.generateAt(sha)
	defer os.RemoveAll(dst)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	b, err := ioutil.ReadFile(path.Join(dst, "scripts/gen/specification.html"))
	if err != nil {
		writeError(w, 500, fmt.Errorf("Error reading spec: %v", err))
		return
	}
	w.Write(b)
}

func checkAuth(pr *PullRequest) error {
	if !pr.User.IsTrusted() {
		return fmt.Errorf("%q is not a trusted pull requester", pr.User.Login)
	}
	return nil
}

func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) {
	pr, err := lookupPullRequest(*req.URL, "/diff/rst")
	if err != nil {
		writeError(w, 400, err)
		return
	}

	// We're going to run whatever Python is specified in the pull request, which
	// may do bad things, so only trust people we trust.
	if err := checkAuth(pr); err != nil {
		writeError(w, 403, err)
		return
	}

	base, err := s.generateAt(pr.Base.SHA)
	defer os.RemoveAll(base)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	head, err := s.generateAt(pr.Head.SHA)
	defer os.RemoveAll(head)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	diffCmd := exec.Command("diff", "-u", path.Join(base, "scripts", "tmp", "full_spec.rst"), path.Join(head, "scripts", "tmp", "full_spec.rst"))
	var diff bytes.Buffer
	diffCmd.Stdout = &diff
	if err := ignoreExitCodeOne(diffCmd.Run()); err != nil {
		writeError(w, 500, fmt.Errorf("error running diff: %v", err))
		return
	}
	w.Write(diff.Bytes())
}

func (s *server) serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
	pr, err := lookupPullRequest(*req.URL, "/diff/html")
	if err != nil {
		writeError(w, 400, err)
		return
	}

	// We're going to run whatever Python is specified in the pull request, which
	// may do bad things, so only trust people we trust.
	if err := checkAuth(pr); err != nil {
		writeError(w, 403, err)
		return
	}

	base, err := s.generateAt(pr.Base.SHA)
	defer os.RemoveAll(base)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	head, err := s.generateAt(pr.Head.SHA)
	defer os.RemoveAll(head)
	if err != nil {
		writeError(w, 500, err)
		return
	}

	htmlDiffer, err := findHTMLDiffer()
	if err != nil {
		writeError(w, 500, fmt.Errorf("could not find HTML differ"))
		return
	}

	cmd := exec.Command(htmlDiffer, path.Join(base, "scripts", "gen", "specification.html"), path.Join(head, "scripts", "gen", "specification.html"))
	var b bytes.Buffer
	cmd.Stdout = &b
	if err := cmd.Run(); err != nil {
		writeError(w, 500, fmt.Errorf("error running HTML differ: %v", err))
		return
	}
	w.Write(b.Bytes())
}

func findHTMLDiffer() (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	differ := path.Join(wd, "htmldiff.pl")
	if _, err := os.Stat(differ); err == nil {
		return differ, nil
	}
	return "", fmt.Errorf("unable to find htmldiff.pl")
}

func listPulls(w http.ResponseWriter, req *http.Request) {
	resp, err := http.Get(pullsPrefix)
	if err != nil {
		writeError(w, 500, err)
		return
	}
	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	var pulls []PullRequest
	if err := dec.Decode(&pulls); err != nil {
		writeError(w, 500, err)
		return
	}
	if len(pulls) == 0 {
		io.WriteString(w, "No pull requests found")
		return
	}
	s := "<body><ul>"
	for _, pull := range pulls {
		s += fmt.Sprintf(`<li>%d: <a href="%s">%s</a>: <a href="%s">%s</a>: <a href="spec/%d">spec</a> <a href="diff/html/%d">spec diff</a> <a href="diff/rst/%d">rst diff</a></li>`,
			pull.Number, pull.User.HTMLURL, pull.User.Login, pull.HTMLURL, pull.Title, pull.Number, pull.Number, pull.Number)
	}
	s += `</ul><div><a href="spec/head">View the spec at head</a></div></body>`
	io.WriteString(w, s)
}

func ignoreExitCodeOne(err error) error {
	if err == nil {
		return err
	}

	if exiterr, ok := err.(*exec.ExitError); ok {
		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
			if status.ExitStatus() == 1 {
				return nil
			}
		}
	}
	return err
}

func main() {
	flag.Parse()
	// It would be great to read this from github, but there's no convenient way to do so.
	// Most of these memberships are "private", so would require some kind of auth.
	allowedMembers = map[string]bool{
		"dbkr":          true,
		"erikjohnston":  true,
		"illicitonion":  true,
		"Kegsay":        true,
		"NegativeMjark": true,
		"richvdh":       true,
	}
	rand.Seed(time.Now().Unix())
	masterCloneDir, err := gitClone(matrixDocCloneURL, false)
	if err != nil {
		log.Fatal(err)
	}
	s := server{masterCloneDir}
	http.HandleFunc("/spec/", s.serveSpec)
	http.HandleFunc("/diff/rst/", s.serveRSTDiff)
	http.HandleFunc("/diff/html/", s.serveHTMLDiff)
	http.HandleFunc("/healthz", serveText("ok"))
	http.HandleFunc("/", listPulls)

	fmt.Printf("Listening on port %d\n", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

func serveText(s string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, s)
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package probes

import (
	"bytes"
	"encoding/binary"
	"io"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcapgo"

	"github.com/pmylund/go-cache"

	"github.com/redhat-cip/skydive/analyzer"
	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/flow"
	"github.com/redhat-cip/skydive/flow/mappings"
	"github.com/redhat-cip/skydive/logging"
	"github.com/redhat-cip/skydive/topology/graph"
)

type PcapProbe struct {
	Filename string

	Graph               *graph.Graph
	AnalyzerClient      *analyzer.Client
	FlowMappingPipeline *mappings.FlowMappingPipeline

	cache            *cache.Cache
	cacheUpdaterChan chan uint32
}

func (probe *PcapProbe) GetTarget() string {
	return "PcapProbe:1234"
}

func (probe *PcapProbe) cacheUpdater() {
	logging.GetLogger().Debug("Start PcapProbe cache updater")

	var index uint32
	for {
		index = <-probe.cacheUpdaterChan

		logging.GetLogger().Debug("PcapProbe request received: %d", index)

		probe.Graph.Lock()

		intfs := probe.Graph.LookupNodes(graph.Metadatas{"IfIndex": index})

		// lookup for the interface that is a part of an ovs bridge
		for _, intf := range intfs {
			ancestors, ok := probe.Graph.GetAncestorsTo(intf, graph.Metadatas{"Type": "ovsbridge"})
			if ok {
				bridge := ancestors[2]
				ancestors, ok = probe.Graph.GetAncestorsTo(bridge, graph.Metadatas{"Type": "host"})

				var path string
				for i := len(ancestors) - 1; i >= 0; i-- {
					if len(path) > 0 {
						path += "/"
					}
					path += ancestors[i].Metadatas()["Name"].(string)
				}
				probe.cache.Set(strconv.FormatUint(uint64(index), 10), path, cache.DefaultExpiration)
				break
			}
		}
		probe.Graph.Unlock()
	}
}

func (probe *PcapProbe) getProbePath(index uint32) *string {
	p, f := probe.cache.Get(strconv.FormatUint(uint64(index), 10))
	if f {
		path := p.(string)
		return &path
	}

	probe.cacheUpdaterChan <- index

	return nil
}

func (probe *PcapProbe) flowExpire(f *flow.Flow) {
	/* send a special event to the analyzer */
}

var nbpackets int = 0
var nbsflowmsg int = 0
var sflowSeq uint32 = 0

func SFlowRawPacketFlowRecordSerialize(rec *layers.SFlowRawPacketFlowRecord, payload []byte) []byte {
	nbBytes := uint32(len(payload))
	rec.FrameLength = nbBytes
	rec.HeaderLength = nbBytes
	rec.FlowDataLength = rec.HeaderLength + 16

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, ((uint32(rec.EnterpriseID) << 12) | (uint32(rec.Format))))
	binary.Write(buf, binary.BigEndian, rec.FlowDataLength)
	binary.Write(buf, binary.BigEndian, rec.HeaderProtocol)
	binary.Write(buf, binary.BigEndian, rec.FrameLength)
	binary.Write(buf, binary.BigEndian, rec.PayloadRemoved)
	binary.Write(buf, binary.BigEndian, rec.HeaderLength)
	buf.Write(payload)
	// Add padding
	npad := (4 - (rec.HeaderLength % 4))
	for ; npad > 0; npad-- {
		buf.Write([]byte{0})
	}
	return buf.Bytes()
}

func SFlowFlowSampleSerialize(sf *layers.SFlowFlowSample, payloads [][]byte) []byte {
	bufRec := new(bytes.Buffer)
	for _, record := range sf.Records {
		rec := record.(layers.SFlowRawPacketFlowRecord)
		for _, payload := range payloads {
			bufRec.Write(SFlowRawPacketFlowRecordSerialize(&rec, payload))
		}
	}
	sf.SampleLength = uint32(bufRec.Len()) + 32
	sf.RecordCount = uint32(len(payloads))

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, ((uint32(sf.EnterpriseID) << 12) | (uint32(sf.Format))))
	binary.Write(buf, binary.BigEndian, sf.SampleLength)
	binary.Write(buf, binary.BigEndian, sf.SequenceNumber)
	binary.Write(buf, binary.BigEndian, ((uint32(sf.SourceIDClass) << 30) | (uint32(sf.SourceIDIndex))))
	binary.Write(buf, binary.BigEndian, sf.SamplingRate)
	binary.Write(buf, binary.BigEndian, sf.SamplePool)
	binary.Write(buf, binary.BigEndian, sf.Dropped)
	binary.Write(buf, binary.BigEndian, sf.InputInterface)
	binary.Write(buf, binary.BigEndian, sf.OutputInterface)
	binary.Write(buf, binary.BigEndian, sf.RecordCount)
	buf.Write(bufRec.Bytes())
	return buf.Bytes()
}

func SFlowDatagramSerialize(sfd *layers.SFlowDatagram, payload [][]byte) []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, sfd.DatagramVersion)
	binary.Write(buf, binary.BigEndian, uint32(layers.SFlowIPv4))
	binary.Write(buf, binary.BigEndian, sfd.AgentAddress)
	binary.Write(buf, binary.BigEndian, sfd.SubAgentID)
	binary.Write(buf, binary.BigEndian, sfd.SequenceNumber)
	binary.Write(buf, binary.BigEndian, sfd.AgentUptime)
	binary.Write(buf, binary.BigEndian, sfd.SampleCount)
	for _, fs := range sfd.FlowSamples {
		buf.Write(SFlowFlowSampleSerialize(&fs, payload))
	}
	return buf.Bytes()
}

func (probe *PcapProbe) sflowPackets(pkts [][]byte) []byte {
	nbsflowmsg++
	sfraw := layers.SFlowRawPacketFlowRecord{
		SFlowBaseFlowRecord: layers.SFlowBaseFlowRecord{
			EnterpriseID: layers.SFlowStandard,
			Format:       layers.SFlowTypeRawPacketFlow,
			//				FlowDataLength uint32
		},
		HeaderProtocol: layers.SFlowProtoEthernet,
		//	FrameLength    uint32
		PayloadRemoved: 0,
		//	HeaderLength   uint32
		//	Header         gopacket.NewPacket
	}

	sflowSeq++

	sf := layers.SFlowFlowSample{
		EnterpriseID: layers.SFlowStandard,
		Format:       layers.SFlowTypeFlowSample,
		//	SampleLength    uint32
		SequenceNumber:  uint32(sflowSeq),
		SourceIDClass:   layers.SFlowTypeSingleInterface,
		SourceIDIndex:   layers.SFlowSourceValue(47),
		SamplingRate:    300,
		SamplePool:      0x12345,
		Dropped:         0,
		InputInterface:  48,
		OutputInterface: 47,
		RecordCount:     1,
		//		Records:
	}
	sf.Records = append(sf.Records, sfraw)
	sf.RecordCount = uint32(len(sf.Records))

	sflowLayer := &layers.SFlowDatagram{
		DatagramVersion: 5,
		AgentAddress:    net.IP{127, 0, 0, 3},
		SubAgentID:      0,
		SequenceNumber:  sflowSeq,
		AgentUptime:     2294190,
		//		SampleCount:     count,
		//		FlowSamples:     sflowsamples,
		//				CounterSamples  []SFlowCounterSample
	}
	sflowLayer.FlowSamples = append(sflowLayer.FlowSamples, sf)
	sflowLayer.SampleCount = uint32(len(sflowLayer.FlowSamples))

	rawBytes := SFlowDatagramSerialize(sflowLayer, pkts)
	return rawBytes
}

func genEthIPUdp(payload []byte) []byte {
	ethernetLayer := &layers.Ethernet{
		SrcMAC: net.HardwareAddr{0x00, 0x01, 0xFF, 0xAA, 0xFA, 0xAA},
		DstMAC: net.HardwareAddr{0x00, 0x01, 0xBD, 0xBD, 0xBD, 0xBD},
	}
	ethernetLayer.EthernetType = layers.EthernetTypeIPv4
	ipLayer := &layers.IPv4{
		SrcIP: net.IP{127, 0, 0, 1},
		DstIP: net.IP{127, 0, 0, 2},
	}
	ipLayer.IHL = 0x45
	ipLayer.Protocol = layers.IPProtocolUDP
	udpLayer := &layers.UDP{
		SrcPort: layers.UDPPort(54321),
		DstPort: layers.UDPPort(6343),
	}
	udpLayer.Length = uint16(8 + len(payload))
	ipLayer.Length = 20 + udpLayer.Length
	ipLayer.TTL = 64
	ipLayer.Id = uint16(0xbbea)

	// And create the packet with the layers
	buffer := gopacket.NewSerializeBuffer()
	options := gopacket.SerializeOptions{}
	gopacket.SerializeLayers(buffer, options,
		ethernetLayer,
		ipLayer,
		udpLayer,
		gopacket.Payload(payload),
	)

	return buffer.Bytes()
}

func writePcap(packet []byte) {
	f, _ := os.Create("/tmp/file.pcap")
	w := pcapgo.NewWriter(f)
	w.WriteFileHeader(65536, layers.LinkTypeEthernet) // new file, must do this.
	w.WritePacket(
		gopacket.CaptureInfo{Timestamp: time.Now(),
			CaptureLength: len(packet),
			Length:        len(packet),
		}, packet)
	f.Close()
}

func (probe *PcapProbe) Start() error {
	f, err := os.Open(probe.Filename)
	if err != nil {
		logging.GetLogger().Fatal("PCAP OpenOffline error (", probe.Filename, ")", err)
	}
	handleRead, err := pcapgo.NewReader(f)
	if err != nil {
		logging.GetLogger().Fatal("PCAP OpenOffline error (handle to read packet)", err)
	}
	defer f.Close()

	// start index/mac cache updater
	go probe.cacheUpdater()

	flowtable := flow.NewFlowTable()
	go flowtable.AsyncExpire(probe.flowExpire, 5*time.Minute)

	var packets [][]byte
	for {
		data, _, err := handleRead.ReadPacketData()
		if err != nil && err != io.EOF {
			logging.GetLogger().Debug("Capture file has been cut in the middle of a packet")
			logging.GetLogger().Fatal(err)
			break
		} else if err == io.EOF {
			logging.GetLogger().Debug("End of capture file")
			break
		} else {
			nbpackets++
			packets = append(packets, data)

			sflowPacketData := probe.sflowPackets(packets)
			packets = packets[:0]

			p := gopacket.NewPacket(sflowPacketData[:], layers.LayerTypeSFlow, gopacket.Default)
			sflowLayer := p.Layer(layers.LayerTypeSFlow)
			sflowPacket, ok := sflowLayer.(*layers.SFlowDatagram)
			if !ok {
				continue
			}

			if sflowPacket.SampleCount > 0 {
				for _, sample := range sflowPacket.FlowSamples {
					replayIntf := "replay0"
					flows := flow.FLowsFromSFlowSample(flowtable, &sample, &replayIntf) // probe.getProbePath(sample.InputInterface))
					logging.GetLogger().Debug("%d flows captured", len(flows))

					if probe.FlowMappingPipeline != nil {
						probe.FlowMappingPipeline.Enhance(flows)
					}

					if probe.AnalyzerClient != nil {
						// FIX(safchain) add flow state cache in order to send only flow changes
						// to not flood the analyzer
						probe.AnalyzerClient.SendFlows(flows)
					}
				}
			}
		}
	}

	return nil
}

func (probe *PcapProbe) SetAnalyzerClient(a *analyzer.Client) {
	probe.AnalyzerClient = a
}

func (probe *PcapProbe) SetMappingPipeline(p *mappings.FlowMappingPipeline) {
	probe.FlowMappingPipeline = p
}

func NewPcapProbe(pcapfilename string, g *graph.Graph) (*PcapProbe, error) {
	probe := &PcapProbe{
		Filename: pcapfilename,
		Graph:    g,
	}

	if probe.Filename == "" {
		probe.Filename = config.GetConfig().Section("agent").Key("pcaptrace").String()
	}

	expire, err := config.GetConfig().Section("cache").Key("expire").Int()
	if err != nil {
		return nil, err
	}
	cleanup, err := config.GetConfig().Section("cache").Key("cleanup").Int()
	if err != nil {
		return nil, err
	}

	probe.cache = cache.New(time.Duration(expire)*time.Second, time.Duration(cleanup)*time.Second)
	probe.cacheUpdaterChan = make(chan uint32, 200)

	return probe, nil
}
<commit_msg>[probe][pcap] fixup SFlow multiple records mode<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package probes

import (
	"bytes"
	"encoding/binary"
	"io"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcapgo"

	"github.com/pmylund/go-cache"

	"github.com/redhat-cip/skydive/analyzer"
	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/flow"
	"github.com/redhat-cip/skydive/flow/mappings"
	"github.com/redhat-cip/skydive/logging"
	"github.com/redhat-cip/skydive/topology/graph"
)

type PcapProbe struct {
	Filename string

	Graph               *graph.Graph
	AnalyzerClient      *analyzer.Client
	FlowMappingPipeline *mappings.FlowMappingPipeline

	cache            *cache.Cache
	cacheUpdaterChan chan uint32
}

func (probe *PcapProbe) GetTarget() string {
	return "PcapProbe:1234"
}

func (probe *PcapProbe) cacheUpdater() {
	logging.GetLogger().Debug("Start PcapProbe cache updater")

	var index uint32
	for {
		index = <-probe.cacheUpdaterChan

		logging.GetLogger().Debug("PcapProbe request received: %d", index)

		probe.Graph.Lock()

		intfs := probe.Graph.LookupNodes(graph.Metadatas{"IfIndex": index})

		// lookup for the interface that is a part of an ovs bridge
		for _, intf := range intfs {
			ancestors, ok := probe.Graph.GetAncestorsTo(intf, graph.Metadatas{"Type": "ovsbridge"})
			if ok {
				bridge := ancestors[2]
				ancestors, ok = probe.Graph.GetAncestorsTo(bridge, graph.Metadatas{"Type": "host"})

				var path string
				for i := len(ancestors) - 1; i >= 0; i-- {
					if len(path) > 0 {
						path += "/"
					}
					path += ancestors[i].Metadatas()["Name"].(string)
				}
				probe.cache.Set(strconv.FormatUint(uint64(index), 10), path, cache.DefaultExpiration)
				break
			}
		}
		probe.Graph.Unlock()
	}
}

func (probe *PcapProbe) getProbePath(index uint32) *string {
	p, f := probe.cache.Get(strconv.FormatUint(uint64(index), 10))
	if f {
		path := p.(string)
		return &path
	}

	probe.cacheUpdaterChan <- index

	return nil
}

func (probe *PcapProbe) flowExpire(f *flow.Flow) {
	/* send a special event to the analyzer */
}

var nbpackets int = 0
var nbsflowmsg int = 0
var sflowSeq uint32 = 0

func SFlowRawPacketFlowRecordSerialize(rec *layers.SFlowRawPacketFlowRecord, payload *[]byte) []byte {
	nbBytes := uint32(len(*payload))
	rec.FrameLength = nbBytes
	rec.HeaderLength = nbBytes
	rec.FlowDataLength = rec.HeaderLength + 16

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, ((uint32(rec.EnterpriseID) << 12) | (uint32(rec.Format))))
	binary.Write(buf, binary.BigEndian, rec.FlowDataLength)
	binary.Write(buf, binary.BigEndian, rec.HeaderProtocol)
	binary.Write(buf, binary.BigEndian, rec.FrameLength)
	binary.Write(buf, binary.BigEndian, rec.PayloadRemoved)
	binary.Write(buf, binary.BigEndian, rec.HeaderLength)
	buf.Write(*payload)
	// Add padding
	headerLenWithPadding := uint32(rec.HeaderLength + ((4 - rec.HeaderLength) % 4))
	npad := headerLenWithPadding - nbBytes
	for ; npad > 0; npad-- {
		buf.Write([]byte{0})
	}
	return buf.Bytes()
}

func SFlowFlowSampleSerialize(sf *layers.SFlowFlowSample, packets *[][]byte) []byte {
	bufRec := new(bytes.Buffer)
	for _, record := range sf.Records {
		rec := record.(layers.SFlowRawPacketFlowRecord)
		for _, payload := range *packets {
			bufRec.Write(SFlowRawPacketFlowRecordSerialize(&rec, &payload))
		}
	}
	sf.SampleLength = uint32(bufRec.Len()) + 32
	sf.RecordCount = uint32(len(*packets))

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, ((uint32(sf.EnterpriseID) << 12) | (uint32(sf.Format))))
	binary.Write(buf, binary.BigEndian, sf.SampleLength)
	binary.Write(buf, binary.BigEndian, sf.SequenceNumber)
	binary.Write(buf, binary.BigEndian, ((uint32(sf.SourceIDClass) << 30) | (uint32(sf.SourceIDIndex))))
	binary.Write(buf, binary.BigEndian, sf.SamplingRate)
	binary.Write(buf, binary.BigEndian, sf.SamplePool)
	binary.Write(buf, binary.BigEndian, sf.Dropped)
	binary.Write(buf, binary.BigEndian, sf.InputInterface)
	binary.Write(buf, binary.BigEndian, sf.OutputInterface)
	binary.Write(buf, binary.BigEndian, sf.RecordCount)
	buf.Write(bufRec.Bytes())
	return buf.Bytes()
}

func SFlowDatagramSerialize(sfd *layers.SFlowDatagram, packets *[][]byte) []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, sfd.DatagramVersion)
	binary.Write(buf, binary.BigEndian, uint32(layers.SFlowIPv4))
	binary.Write(buf, binary.BigEndian, sfd.AgentAddress)
	binary.Write(buf, binary.BigEndian, sfd.SubAgentID)
	binary.Write(buf, binary.BigEndian, sfd.SequenceNumber)
	binary.Write(buf, binary.BigEndian, sfd.AgentUptime)
	binary.Write(buf, binary.BigEndian, sfd.SampleCount)
	for _, fs := range sfd.FlowSamples {
		buf.Write(SFlowFlowSampleSerialize(&fs, packets))
	}
	return buf.Bytes()
}

func (probe *PcapProbe) sflowPackets(packets *[][]byte) []byte {
	nbsflowmsg++
	sfraw := layers.SFlowRawPacketFlowRecord{
		SFlowBaseFlowRecord: layers.SFlowBaseFlowRecord{
			EnterpriseID: layers.SFlowStandard,
			Format:       layers.SFlowTypeRawPacketFlow,
			//				FlowDataLength uint32
		},
		HeaderProtocol: layers.SFlowProtoEthernet,
		//	FrameLength    uint32
		PayloadRemoved: 0,
		//	HeaderLength   uint32
		//	Header         gopacket.NewPacket
	}

	sflowSeq++

	sf := layers.SFlowFlowSample{
		EnterpriseID: layers.SFlowStandard,
		Format:       layers.SFlowTypeFlowSample,
		//	SampleLength    uint32
		SequenceNumber:  uint32(sflowSeq),
		SourceIDClass:   layers.SFlowTypeSingleInterface,
		SourceIDIndex:   layers.SFlowSourceValue(47),
		SamplingRate:    300,
		SamplePool:      0x12345,
		Dropped:         0,
		InputInterface:  48,
		OutputInterface: 47,
		//		RecordCount:     1,
		//		Records:
	}
	sf.Records = append(sf.Records, sfraw)
	sf.RecordCount = uint32(len(sf.Records))

	sflowLayer := &layers.SFlowDatagram{
		DatagramVersion: 5,
		AgentAddress:    net.IP{127, 0, 0, 3},
		SubAgentID:      0,
		SequenceNumber:  sflowSeq,
		AgentUptime:     2294190,
		//		SampleCount:     count,
		//		FlowSamples:     sflowsamples,
		//				CounterSamples  []SFlowCounterSample
	}
	sflowLayer.FlowSamples = append(sflowLayer.FlowSamples, sf)
	sflowLayer.SampleCount = uint32(len(sflowLayer.FlowSamples))

	rawBytes := SFlowDatagramSerialize(sflowLayer, packets)
	return rawBytes
}

func genEthIPUdp(payload []byte) []byte {
	ethernetLayer := &layers.Ethernet{
		SrcMAC: net.HardwareAddr{0x00, 0x01, 0xFF, 0xAA, 0xFA, 0xAA},
		DstMAC: net.HardwareAddr{0x00, 0x01, 0xBD, 0xBD, 0xBD, 0xBD},
	}
	ethernetLayer.EthernetType = layers.EthernetTypeIPv4
	ipLayer := &layers.IPv4{
		SrcIP: net.IP{127, 0, 0, 1},
		DstIP: net.IP{127, 0, 0, 2},
	}
	ipLayer.IHL = 0x45
	ipLayer.Protocol = layers.IPProtocolUDP
	udpLayer := &layers.UDP{
		SrcPort: layers.UDPPort(54321),
		DstPort: layers.UDPPort(6343),
	}
	udpLayer.Length = uint16(8 + len(payload))
	ipLayer.Length = 20 + udpLayer.Length
	ipLayer.TTL = 64
	ipLayer.Id = uint16(0xbbea)

	// And create the packet with the layers
	buffer := gopacket.NewSerializeBuffer()
	options := gopacket.SerializeOptions{}
	gopacket.SerializeLayers(buffer, options,
		ethernetLayer,
		ipLayer,
		udpLayer,
		gopacket.Payload(payload),
	)

	return buffer.Bytes()
}

func writePcap(packet []byte) {
	logging.GetLogger().Debug("Writing PCAP file")
	f, _ := os.Create("/tmp/file.pcap")
	w := pcapgo.NewWriter(f)
	w.WriteFileHeader(65536, layers.LinkTypeEthernet) // new file, must do this.
	w.WritePacket(
		gopacket.CaptureInfo{Timestamp: time.Now(),
			CaptureLength: len(packet),
			Length:        len(packet),
		}, packet)
	f.Close()
}

func (probe *PcapProbe) AsyncProgressInfo() {
	ticker := time.NewTicker(10 * time.Second)
	defer func() {
		ticker.Stop()
	}()
	for {
		select {
		case <-ticker.C:
			logging.GetLogger().Debug("%d", nbpackets)
		}
	}
}

func (probe *PcapProbe) Start() error {
	f, err := os.Open(probe.Filename)
	if err != nil {
		logging.GetLogger().Fatal("PCAP OpenOffline error (", probe.Filename, ")", err)
	}
	handleRead, err := pcapgo.NewReader(f)
	if err != nil {
		logging.GetLogger().Fatal("PCAP OpenOffline error (handle to read packet)", err)
	}
	defer f.Close()

	// start index/mac cache updater
	go probe.cacheUpdater()

	flowtable := flow.NewFlowTable()
	go flowtable.AsyncExpire(probe.flowExpire, 5*time.Minute)

	go probe.AsyncProgressInfo()

	var packets [][]byte
	for {
		data, _, err := handleRead.ReadPacketData()
		if err != nil && err != io.EOF {
			logging.GetLogger().Debug("Capture file has been cut in the middle of a packet")
			logging.GetLogger().Fatal(err)
			break
		} else if err == io.EOF {
			logging.GetLogger().Debug("End of capture file")
			break
		} else {
			nbpackets++
			dataCopy := make([]byte, len(data))
			copy(dataCopy, data)
			packets = append(packets, dataCopy)

			if (nbpackets % 5) != 0 {
				continue
			}

			sflowPacketData := probe.sflowPackets(&packets)
			packets = packets[:0]

			p := gopacket.NewPacket(sflowPacketData[:], layers.LayerTypeSFlow, gopacket.Default)
			sflowLayer := p.Layer(layers.LayerTypeSFlow)
			sflowPacket, ok := sflowLayer.(*layers.SFlowDatagram)
			if !ok {
				logging.GetLogger().Critical("Can't cast gopacket as a SFlowDatagram", p)
				continue
			}

			if sflowPacket.SampleCount > 0 {
				for _, sample := range sflowPacket.FlowSamples {
					replayIntf := "replay0"
					flows := flow.FLowsFromSFlowSample(flowtable, &sample, &replayIntf) // probe.getProbePath(sample.InputInterface))
					logging.GetLogger().Debug("%d flows captured", len(flows))

					if probe.FlowMappingPipeline != nil {
						probe.FlowMappingPipeline.Enhance(flows)
					}

					if probe.AnalyzerClient != nil {
						// FIX(safchain) add flow state cache in order to send only flow changes
						// to not flood the analyzer
						probe.AnalyzerClient.SendFlows(flows)
					}
				}
			}
		}
	}

	return nil
}

func (probe *PcapProbe) SetAnalyzerClient(a *analyzer.Client) {
	probe.AnalyzerClient = a
}

func (probe *PcapProbe) SetMappingPipeline(p *mappings.FlowMappingPipeline) {
	probe.FlowMappingPipeline = p
}

func NewPcapProbe(pcapfilename string, g *graph.Graph) (*PcapProbe, error) {
	probe := &PcapProbe{
		Filename: pcapfilename,
		Graph:    g,
	}

	if probe.Filename == "" {
		probe.Filename = config.GetConfig().Section("agent").Key("pcaptrace").String()
	}

	expire, err := config.GetConfig().Section("cache").Key("expire").Int()
	if err != nil {
		return nil, err
	}
	cleanup, err := config.GetConfig().Section("cache").Key("cleanup").Int()
	if err != nil {
		return nil, err
	}

	probe.cache = cache.New(time.Duration(expire)*time.Second, time.Duration(cleanup)*time.Second)
	probe.cacheUpdaterChan = make(chan uint32, 200)

	return probe, nil
}
<|endoftext|>


<commit_before>package asset

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"github.com/GlenKelley/go-collada"
	"github.com/goxjs/gl"
	"github.com/omustardo/gome/model/mesh"
	"github.com/omustardo/gome/util/bytecoder"
)

func LoadDAE(path string) (mesh.Mesh, error) {
	data, err := loadFile(path)
	if err != nil {
		return mesh.Mesh{}, err
	}
	return loadDAEData(data)
}

func loadDAEData(data []byte) (mesh.Mesh, error) {
	reader := bytes.NewBuffer(data)

	doc, err := collada.LoadDocumentFromReader(reader)
	if err != nil {
		return mesh.Mesh{}, err
	}

	var m_TriangleCount int
	// Calculate the total triangle and line counts.
	for _, geometry := range doc.LibraryGeometries[0].Geometry {
		for _, triangle := range geometry.Mesh.Triangles {
			m_TriangleCount += triangle.HasCount.Count
		}
	}

	vertices := make([]float32, 3*3*m_TriangleCount)
	normals := make([]float32, 3*3*m_TriangleCount)

	nTriangleNumber := 0
	for _, geometry := range doc.LibraryGeometries[0].Geometry {
		if len(geometry.Mesh.Triangles) == 0 {
			continue
		}

		// HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
		pVertexData := geometry.Mesh.Source[0].FloatArray.F32()
		pNormalData := geometry.Mesh.Source[1].FloatArray.F32()

		unsharedCount := len(geometry.Mesh.Vertices.Input)

		for _, triangles := range geometry.Mesh.Triangles {
			sharedIndicies := triangles.HasP.P.I()
			sharedCount := len(triangles.HasSharedInput.Input)

			for i := 0; i < triangles.HasCount.Count; i++ {
				offset := 0 // HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
				vertices[3*3*nTriangleNumber+0] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+1] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+2] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+2]
				vertices[3*3*nTriangleNumber+3] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+4] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+5] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+2]
				vertices[3*3*nTriangleNumber+6] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+7] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+8] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+2]

				if unsharedCount*sharedCount == 2 {
					offset = sharedCount - 1 // HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
					normals[3*3*nTriangleNumber+0] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+1] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+2] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+2]
					normals[3*3*nTriangleNumber+3] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+4] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+5] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+2]
					normals[3*3*nTriangleNumber+6] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+7] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+8] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+2]
				}

				nTriangleNumber++
			}
		}
	}

	vertexVBO := gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, vertexVBO)
	gl.BufferData(gl.ARRAY_BUFFER, bytecoder.Float32(binary.LittleEndian, vertices...), gl.STATIC_DRAW)

	normalVBO := gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, normalVBO)
	gl.BufferData(gl.ARRAY_BUFFER, bytecoder.Float32(binary.LittleEndian, normals...), gl.STATIC_DRAW)

	if glError := gl.GetError(); glError != 0 {
		return mesh.Mesh{}, fmt.Errorf("gl.GetError: %v", glError)
	}

	return mesh.NewMesh(vertexVBO, gl.Buffer{}, normalVBO, gl.TRIANGLES, 3*m_TriangleCount, nil, gl.Texture{}, gl.Buffer{}), nil
}
<commit_msg>Add TODO for DAE loading<commit_after>package asset

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"github.com/GlenKelley/go-collada"
	"github.com/goxjs/gl"
	"github.com/omustardo/gome/model/mesh"
	"github.com/omustardo/gome/util/bytecoder"
)

func LoadDAE(path string) (mesh.Mesh, error) {
	data, err := loadFile(path)
	if err != nil {
		return mesh.Mesh{}, err
	}
	return loadDAEData(data)
}

// TODO: Fully implement DAE loading. Currently only supports the basic vertex mesh. Texture mapping, normals, and advanced DAE features are not implemented.
// This uses https://github.com/GlenKelley/go-collada which doesn't appear to be complete.
// In generally DAE appears to be less than well supported, so this is a low priority.
func loadDAEData(data []byte) (mesh.Mesh, error) {
	reader := bytes.NewBuffer(data)

	doc, err := collada.LoadDocumentFromReader(reader)
	if err != nil {
		return mesh.Mesh{}, err
	}

	var m_TriangleCount int
	// Calculate the total triangle and line counts.
	for _, geometry := range doc.LibraryGeometries[0].Geometry {
		for _, triangle := range geometry.Mesh.Triangles {
			m_TriangleCount += triangle.HasCount.Count
		}
	}

	vertices := make([]float32, 3*3*m_TriangleCount)
	normals := make([]float32, 3*3*m_TriangleCount)

	nTriangleNumber := 0
	for _, geometry := range doc.LibraryGeometries[0].Geometry {
		if len(geometry.Mesh.Triangles) == 0 {
			continue
		}

		// HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
		pVertexData := geometry.Mesh.Source[0].FloatArray.F32()
		pNormalData := geometry.Mesh.Source[1].FloatArray.F32()

		unsharedCount := len(geometry.Mesh.Vertices.Input)

		for _, triangles := range geometry.Mesh.Triangles {
			sharedIndicies := triangles.HasP.P.I()
			sharedCount := len(triangles.HasSharedInput.Input)

			for i := 0; i < triangles.HasCount.Count; i++ {
				offset := 0 // HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
				vertices[3*3*nTriangleNumber+0] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+1] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+2] = pVertexData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+2]
				vertices[3*3*nTriangleNumber+3] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+4] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+5] = pVertexData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+2]
				vertices[3*3*nTriangleNumber+6] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+0]
				vertices[3*3*nTriangleNumber+7] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+1]
				vertices[3*3*nTriangleNumber+8] = pVertexData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+2]

				if unsharedCount*sharedCount == 2 {
					offset = sharedCount - 1 // HACK. 0 seems to be position, 1 is normal, but need to not hardcode this.
					normals[3*3*nTriangleNumber+0] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+1] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+2] = pNormalData[3*sharedIndicies[(3*i+0)*sharedCount+offset]+2]
					normals[3*3*nTriangleNumber+3] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+4] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+5] = pNormalData[3*sharedIndicies[(3*i+1)*sharedCount+offset]+2]
					normals[3*3*nTriangleNumber+6] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+0]
					normals[3*3*nTriangleNumber+7] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+1]
					normals[3*3*nTriangleNumber+8] = pNormalData[3*sharedIndicies[(3*i+2)*sharedCount+offset]+2]
				}

				nTriangleNumber++
			}
		}
	}

	vertexVBO := gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, vertexVBO)
	gl.BufferData(gl.ARRAY_BUFFER, bytecoder.Float32(binary.LittleEndian, vertices...), gl.STATIC_DRAW)

	normalVBO := gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, normalVBO)
	gl.BufferData(gl.ARRAY_BUFFER, bytecoder.Float32(binary.LittleEndian, normals...), gl.STATIC_DRAW)

	if glError := gl.GetError(); glError != 0 {
		return mesh.Mesh{}, fmt.Errorf("gl.GetError: %v", glError)
	}

	return mesh.NewMesh(vertexVBO, gl.Buffer{}, normalVBO, gl.TRIANGLES, 3*m_TriangleCount, nil, gl.Texture{}, gl.Buffer{}), nil
}
<|endoftext|>