<commit_before>// Copyright (c) 2021 - The Event Horizon authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kafka

import (
	"context"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/segmentio/kafka-go"

	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/codec/json"
)

// EventBus is a local event bus that delegates handling of published events
// to all matching registered handlers, in order of registration.
type EventBus struct {
	// TODO: Support multiple brokers.
	addr         string
	appID        string
	topic        string
	startOffset  int64
	client       *kafka.Client
	writer       *kafka.Writer
	registered   map[eh.EventHandlerType]struct{}
	registeredMu sync.RWMutex
	errCh        chan error
	cctx         context.Context
	cancel       context.CancelFunc
	wg           sync.WaitGroup
	codec        eh.EventCodec
}

// NewEventBus creates an EventBus, with optional GCP connection settings.
func NewEventBus(addr, appID string, options ...Option) (*EventBus, error) {
	ctx, cancel := context.WithCancel(context.Background())

	b := &EventBus{
		addr:        addr,
		appID:       appID,
		topic:       appID + "_events",
		startOffset: kafka.LastOffset, // Default: Don't read old messages.
		registered:  map[eh.EventHandlerType]struct{}{},
		errCh:       make(chan error, 100),
		cctx:        ctx,
		cancel:      cancel,
		codec:       &json.EventCodec{},
	}

	// Apply configuration options.
	for _, option := range options {
		if option == nil {
			continue
		}

		if err := option(b); err != nil {
			return nil, fmt.Errorf("error while applying option: %w", err)
		}
	}

	// Get or create the topic.
	b.client = &kafka.Client{
		Addr: kafka.TCP(addr),
	}

	var resp *kafka.CreateTopicsResponse

	var err error

	for i := 0; i < 10; i++ {
		resp, err = b.client.CreateTopics(context.Background(), &kafka.CreateTopicsRequest{
			Topics: []kafka.TopicConfig{{
				Topic:             b.topic,
				NumPartitions:     5,
				ReplicationFactor: 1,
			}},
		})

		if errors.Is(err, kafka.BrokerNotAvailable) {
			time.Sleep(5 * time.Second)

			continue
		} else if err != nil {
			return nil, fmt.Errorf("error creating Kafka topic: %w", err)
		} else {
			break
		}
	}

	if resp == nil {
		return nil, fmt.Errorf("could not get/create Kafka topic in time: %w", err)
	}

	if topicErr, ok := resp.Errors[b.topic]; ok && topicErr != nil {
		if !errors.Is(topicErr, kafka.TopicAlreadyExists) {
			return nil, fmt.Errorf("invalid Kafka topic: %w", topicErr)
		}
	}

	b.writer = &kafka.Writer{
		Addr:         kafka.TCP(addr),
		Topic:        b.topic,
		BatchSize:    1,                // Write every event to the bus without delay.
		RequiredAcks: kafka.RequireOne, // Stronger consistency.
		Balancer:     &kafka.Hash{},    // Hash by aggregate ID.
	}

	return b, nil
}

// Option is an option setter used to configure creation.
type Option func(*EventBus) error

// WithCodec uses the specified codec for encoding events.
func WithCodec(codec eh.EventCodec) Option {
	return func(b *EventBus) error {
		b.codec = codec

		return nil
	}
}

// WithStartOffset sets the consumer group's offset to start at
// Defaults to: LastOffset
// Per the kafka client documentation
//     StartOffset determines from whence the consumer group should begin
//     consuming when it finds a partition without a committed offset.  If
//     non-zero, it must be set to one of FirstOffset or LastOffset.
func WithStartOffset(startOffset int64) Option {
	return func(b *EventBus) error {
		b.startOffset = startOffset
		return nil
	}
}

// WithTopic uses the specified topic for the event bus topic name
//
// Defaults to: appID + "_events"
func WithTopic(topic string) Option {
	return func(b *EventBus) error {
		b.topic = topic
		return nil
	}
}

// HandlerType implements the HandlerType method of the eventhorizon.EventHandler interface.
func (b *EventBus) HandlerType() eh.EventHandlerType {
	return "eventbus"
}

const (
	aggregateTypeHeader = "aggregate_type"
	eventTypeHeader     = "event_type"
)

// HandleEvent implements the HandleEvent method of the eventhorizon.EventHandler interface.
func (b *EventBus) HandleEvent(ctx context.Context, event eh.Event) error {
	data, err := b.codec.MarshalEvent(ctx, event)
	if err != nil {
		return fmt.Errorf("could not marshal event: %w", err)
	}

	if err := b.writer.WriteMessages(ctx, kafka.Message{
		Key:   []byte(event.AggregateID().String()),
		Value: data,
		Headers: []kafka.Header{
			{
				Key:   aggregateTypeHeader,
				Value: []byte(event.AggregateType().String()),
			},
			{
				Key:   eventTypeHeader,
				Value: []byte(event.EventType().String()),
			},
		},
	}); err != nil {
		return fmt.Errorf("could not publish event: %w", err)
	}

	return nil
}

// AddHandler implements the AddHandler method of the eventhorizon.EventBus interface.
func (b *EventBus) AddHandler(ctx context.Context, m eh.EventMatcher, h eh.EventHandler) error {
	if m == nil {
		return eh.ErrMissingMatcher
	}

	if h == nil {
		return eh.ErrMissingHandler
	}

	// Check handler existence.
	b.registeredMu.Lock()
	defer b.registeredMu.Unlock()

	if _, ok := b.registered[h.HandlerType()]; ok {
		return eh.ErrHandlerAlreadyAdded
	}

	// Get or create the subscription.
	groupID := b.appID + "_" + h.HandlerType().String()
	r := kafka.NewReader(kafka.ReaderConfig{
		Brokers:               []string{b.addr},
		Topic:                 b.topic,
		GroupID:               groupID,     // Send messages to only one subscriber per group.
		MaxWait:               time.Second, // Allow to exit readloop in max 1s.
		WatchPartitionChanges: true,
		StartOffset:           b.startOffset,
	})

	req := &kafka.ListGroupsRequest{
		Addr: b.client.Addr,
	}

	exist := false

	for i := 0; i < 20; i++ {
		resp, err := b.client.ListGroups(ctx, req)
		if err != nil || resp.Error != nil {
			return fmt.Errorf("could not list Kafka groups: %w", err)
		}

		for _, grp := range resp.Groups {
			if grp.GroupID == groupID {
				exist = true

				break
			}
		}

		if exist {
			break
		}

		time.Sleep(500 * time.Millisecond)
	}

	if !exist {
		return fmt.Errorf("did not join group in time")
	}

	// Register handler.
	b.registered[h.HandlerType()] = struct{}{}

	b.wg.Add(1)

	// Handle until context is cancelled.
	go b.handle(m, h, r)

	return nil
}

// Errors implements the Errors method of the eventhorizon.EventBus interface.
func (b *EventBus) Errors() <-chan error {
	return b.errCh
}

// Close implements the Close method of the eventhorizon.EventBus interface.
func (b *EventBus) Close() error {
	b.cancel()
	b.wg.Wait()

	return b.writer.Close()
}

// Handles all events coming in on the channel.
func (b *EventBus) handle(m eh.EventMatcher, h eh.EventHandler, r *kafka.Reader) {
	defer b.wg.Done()

	handler := b.handler(m, h, r)

	for {
		select {
		case <-b.cctx.Done():
			break
		default:
		}

		msg, err := r.FetchMessage(b.cctx)
		if errors.Is(err, context.Canceled) {
			break
		} else if err != nil {
			err = fmt.Errorf("could not fetch message: %w", err)
			select {
			case b.errCh <- &eh.EventBusError{Err: err}:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}

			// Retry the receive loop if there was an error.
			time.Sleep(time.Second)

			continue
		}

		if err := handler(b.cctx, msg); err != nil {
			select {
			case b.errCh <- err:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}

			continue
		}

		// Use a new context to always finish the commit.
		if err := r.CommitMessages(context.Background(), msg); err != nil {
			err = fmt.Errorf("could not commit message: %w", err)
			select {
			case b.errCh <- &eh.EventBusError{Err: err}:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}
		}
	}

	if err := r.Close(); err != nil {
		log.Printf("eventhorizon: failed to close Kafka reader: %s", err)
	}
}

func (b *EventBus) handler(m eh.EventMatcher, h eh.EventHandler, r *kafka.Reader) func(ctx context.Context, msg kafka.Message) *eh.EventBusError {
	return func(ctx context.Context, msg kafka.Message) *eh.EventBusError {
		event, ctx, err := b.codec.UnmarshalEvent(ctx, msg.Value)
		if err != nil {
			return &eh.EventBusError{
				Err: fmt.Errorf("could not unmarshal event: %w", err),
				Ctx: ctx,
			}
		}

		// Ignore non-matching events.
		if !m.Match(event) {
			return nil
		}

		// Handle the event if it did match.
		if err := h.HandleEvent(ctx, event); err != nil {
			return &eh.EventBusError{
				Err:   fmt.Errorf("could not handle event (%s): %w", h.HandlerType(), err),
				Ctx:   ctx,
				Event: event,
			}
		}

		return nil
	}
}
<commit_msg>support clustered kafka brokers in the form of string delimited list<commit_after>// Copyright (c) 2021 - The Event Horizon authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kafka

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/segmentio/kafka-go"

	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/codec/json"
)

// EventBus is a local event bus that delegates handling of published events
// to all matching registered handlers, in order of registration.
type EventBus struct {
	// TODO: Support multiple brokers.
	addr         string // comma delimited list of brokers
	appID        string
	topic        string
	startOffset  int64
	client       *kafka.Client
	writer       *kafka.Writer
	registered   map[eh.EventHandlerType]struct{}
	registeredMu sync.RWMutex
	errCh        chan error
	cctx         context.Context
	cancel       context.CancelFunc
	wg           sync.WaitGroup
	codec        eh.EventCodec
}

// NewEventBus creates an EventBus, with optional GCP connection settings.
func NewEventBus(addr, appID string, options ...Option) (*EventBus, error) {
	ctx, cancel := context.WithCancel(context.Background())

	b := &EventBus{
		addr:        addr,
		appID:       appID,
		topic:       appID + "_events",
		startOffset: kafka.LastOffset, // Default: Don't read old messages.
		registered:  map[eh.EventHandlerType]struct{}{},
		errCh:       make(chan error, 100),
		cctx:        ctx,
		cancel:      cancel,
		codec:       &json.EventCodec{},
	}

	// Apply configuration options.
	for _, option := range options {
		if option == nil {
			continue
		}

		if err := option(b); err != nil {
			return nil, fmt.Errorf("error while applying option: %w", err)
		}
	}

	// Get or create the topic.
	b.client = &kafka.Client{
		Addr: kafka.TCP(addr),
	}

	var resp *kafka.CreateTopicsResponse

	var err error

	for i := 0; i < 10; i++ {
		resp, err = b.client.CreateTopics(context.Background(), &kafka.CreateTopicsRequest{
			Topics: []kafka.TopicConfig{{
				Topic:             b.topic,
				NumPartitions:     5,
				ReplicationFactor: 1,
			}},
		})

		if errors.Is(err, kafka.BrokerNotAvailable) {
			time.Sleep(5 * time.Second)

			continue
		} else if err != nil {
			return nil, fmt.Errorf("error creating Kafka topic: %w", err)
		} else {
			break
		}
	}

	if resp == nil {
		return nil, fmt.Errorf("could not get/create Kafka topic in time: %w", err)
	}

	if topicErr, ok := resp.Errors[b.topic]; ok && topicErr != nil {
		if !errors.Is(topicErr, kafka.TopicAlreadyExists) {
			return nil, fmt.Errorf("invalid Kafka topic: %w", topicErr)
		}
	}

	b.writer = &kafka.Writer{
		Addr:         kafka.TCP(addr),
		Topic:        b.topic,
		BatchSize:    1,                // Write every event to the bus without delay.
		RequiredAcks: kafka.RequireOne, // Stronger consistency.
		Balancer:     &kafka.Hash{},    // Hash by aggregate ID.
	}

	return b, nil
}

// Option is an option setter used to configure creation.
type Option func(*EventBus) error

// WithCodec uses the specified codec for encoding events.
func WithCodec(codec eh.EventCodec) Option {
	return func(b *EventBus) error {
		b.codec = codec

		return nil
	}
}

// WithStartOffset sets the consumer group's offset to start at
// Defaults to: LastOffset
// Per the kafka client documentation
//     StartOffset determines from whence the consumer group should begin
//     consuming when it finds a partition without a committed offset.  If
//     non-zero, it must be set to one of FirstOffset or LastOffset.
func WithStartOffset(startOffset int64) Option {
	return func(b *EventBus) error {
		b.startOffset = startOffset
		return nil
	}
}

// WithTopic uses the specified topic for the event bus topic name
//
// Defaults to: appID + "_events"
func WithTopic(topic string) Option {
	return func(b *EventBus) error {
		b.topic = topic
		return nil
	}
}

// HandlerType implements the HandlerType method of the eventhorizon.EventHandler interface.
func (b *EventBus) HandlerType() eh.EventHandlerType {
	return "eventbus"
}

const (
	aggregateTypeHeader = "aggregate_type"
	eventTypeHeader     = "event_type"
)

// HandleEvent implements the HandleEvent method of the eventhorizon.EventHandler interface.
func (b *EventBus) HandleEvent(ctx context.Context, event eh.Event) error {
	data, err := b.codec.MarshalEvent(ctx, event)
	if err != nil {
		return fmt.Errorf("could not marshal event: %w", err)
	}

	if err := b.writer.WriteMessages(ctx, kafka.Message{
		Key:   []byte(event.AggregateID().String()),
		Value: data,
		Headers: []kafka.Header{
			{
				Key:   aggregateTypeHeader,
				Value: []byte(event.AggregateType().String()),
			},
			{
				Key:   eventTypeHeader,
				Value: []byte(event.EventType().String()),
			},
		},
	}); err != nil {
		return fmt.Errorf("could not publish event: %w", err)
	}

	return nil
}

// AddHandler implements the AddHandler method of the eventhorizon.EventBus interface.
func (b *EventBus) AddHandler(ctx context.Context, m eh.EventMatcher, h eh.EventHandler) error {
	if m == nil {
		return eh.ErrMissingMatcher
	}

	if h == nil {
		return eh.ErrMissingHandler
	}

	// Check handler existence.
	b.registeredMu.Lock()
	defer b.registeredMu.Unlock()

	if _, ok := b.registered[h.HandlerType()]; ok {
		return eh.ErrHandlerAlreadyAdded
	}

	// Get or create the subscription.
	groupID := b.appID + "_" + h.HandlerType().String()

	r := kafka.NewReader(kafka.ReaderConfig{
		Brokers:               strings.Split(b.addr, ","),
		Topic:                 b.topic,
		GroupID:               groupID,     // Send messages to only one subscriber per group.
		MaxWait:               time.Second, // Allow to exit readloop in max 1s.
		WatchPartitionChanges: true,
		StartOffset:           b.startOffset,
	})

	req := &kafka.ListGroupsRequest{
		Addr: b.client.Addr,
	}

	exist := false

	for i := 0; i < 20; i++ {
		resp, err := b.client.ListGroups(ctx, req)
		if err != nil || resp.Error != nil {
			return fmt.Errorf("could not list Kafka groups: %w", err)
		}

		for _, grp := range resp.Groups {
			if grp.GroupID == groupID {
				exist = true

				break
			}
		}

		if exist {
			break
		}

		time.Sleep(500 * time.Millisecond)
	}

	if !exist {
		return fmt.Errorf("did not join group in time")
	}

	// Register handler.
	b.registered[h.HandlerType()] = struct{}{}

	b.wg.Add(1)

	// Handle until context is cancelled.
	go b.handle(m, h, r)

	return nil
}

// Errors implements the Errors method of the eventhorizon.EventBus interface.
func (b *EventBus) Errors() <-chan error {
	return b.errCh
}

// Close implements the Close method of the eventhorizon.EventBus interface.
func (b *EventBus) Close() error {
	b.cancel()
	b.wg.Wait()

	return b.writer.Close()
}

// Handles all events coming in on the channel.
func (b *EventBus) handle(m eh.EventMatcher, h eh.EventHandler, r *kafka.Reader) {
	defer b.wg.Done()

	handler := b.handler(m, h, r)

	for {
		select {
		case <-b.cctx.Done():
			break
		default:
		}

		msg, err := r.FetchMessage(b.cctx)
		if errors.Is(err, context.Canceled) {
			break
		} else if err != nil {
			err = fmt.Errorf("could not fetch message: %w", err)
			select {
			case b.errCh <- &eh.EventBusError{Err: err}:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}

			// Retry the receive loop if there was an error.
			time.Sleep(time.Second)

			continue
		}

		if err := handler(b.cctx, msg); err != nil {
			select {
			case b.errCh <- err:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}

			continue
		}

		// Use a new context to always finish the commit.
		if err := r.CommitMessages(context.Background(), msg); err != nil {
			err = fmt.Errorf("could not commit message: %w", err)
			select {
			case b.errCh <- &eh.EventBusError{Err: err}:
			default:
				log.Printf("eventhorizon: missed error in Kafka event bus: %s", err)
			}
		}
	}

	if err := r.Close(); err != nil {
		log.Printf("eventhorizon: failed to close Kafka reader: %s", err)
	}
}

func (b *EventBus) handler(m eh.EventMatcher, h eh.EventHandler, r *kafka.Reader) func(ctx context.Context, msg kafka.Message) *eh.EventBusError {
	return func(ctx context.Context, msg kafka.Message) *eh.EventBusError {
		event, ctx, err := b.codec.UnmarshalEvent(ctx, msg.Value)
		if err != nil {
			return &eh.EventBusError{
				Err: fmt.Errorf("could not unmarshal event: %w", err),
				Ctx: ctx,
			}
		}

		// Ignore non-matching events.
		if !m.Match(event) {
			return nil
		}

		// Handle the event if it did match.
		if err := h.HandleEvent(ctx, event); err != nil {
			return &eh.EventBusError{
				Err:   fmt.Errorf("could not handle event (%s): %w", h.HandlerType(), err),
				Ctx:   ctx,
				Event: event,
			}
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package eventwriter_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"

	"code.cloudfoundry.org/lager"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry-community/splunk-firehose-nozzle/eventwriter"
)

var _ = Describe("Splunk", func() {
	var (
		testServer      *httptest.Server
		capturedRequest *http.Request
		capturedBody    []byte
		splunkResponse  []byte
		logger          lager.Logger
		config          *SplunkConfig
	)

	BeforeEach(func() {
		logger = lager.NewLogger("test")
		config = &SplunkConfig{
			Token:   "token",
			Index:   "",
			Fields:  nil,
			SkipSSL: true,
			Logger:  logger,
		}
	})

	Context("success response", func() {
		BeforeEach(func() {
			capturedRequest = nil

			splunkResponse = []byte("{}")
			testServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
				capturedRequest = request
				body, err := ioutil.ReadAll(request.Body)
				if err != nil {
					panic(err)
				}
				capturedBody = body

				writer.Write(splunkResponse)
			}))

			config.Host = testServer.URL
		})

		AfterEach(func() {
			testServer.Close()
		})

		It("correctly authenticates requests", func() {
			tokenValue := "abc-some-random-token"
			config.Token = tokenValue

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			authValue := capturedRequest.Header.Get("Authorization")
			expectedAuthValue := fmt.Sprintf("Splunk %s", tokenValue)

			Expect(authValue).To(Equal(expectedAuthValue))
		})

		It("sets content type to json", func() {
			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			contentType := capturedRequest.Header.Get("Content-Type")
			Expect(contentType).To(Equal("application/json"))
		})

		It("sets app name to appName", func() {
			appName := "Splunk Firehose Nozzle"

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			applicationName := capturedRequest.Header.Get("__splunk_app_name")
			Expect(applicationName).To(Equal(appName))

		})

		It("sets app appVersion", func() {
			appVersion := "1.2.2"

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			applicationVersion := capturedRequest.Header.Get("__splunk_app_version")
			Expect(applicationVersion).To(Equal(appVersion))

		})

		It("Writes batch event json", func() {
			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}
			event3 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello pluto",
			}}

			events := []map[string]interface{}{event1, event2, event3}
			err, sentCount := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())
			Expect(sentCount).To(Equal(uint64(3)))

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"}}

{"event":{"greeting":"hello mars"}}

{"event":{"greeting":"hello pluto"}}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))
		})

		It("sets index in splunk payload", func() {
			config.Index = "index_cf"
			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}

			events := []map[string]interface{}{event1, event2}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"},"index":"index_cf"}

{"event":{"greeting":"hello mars"},"index":"index_cf"}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))
		})

		It("adds fields to splunk palylaod", func() {
			fields := map[string]string{
				"foo":   "bar",
				"hello": "world",
			}
			config.Fields = fields

			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}

			events := []map[string]interface{}{event1, event2}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"},"fields":{"foo":"bar","hello":"world"}}

{"event":{"greeting":"hello mars"},"fields":{"foo":"bar","hello":"world"}}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))

		})

		It("Writes to correct endpoint", func() {
			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest.URL.Path).To(Equal("/services/collector"))
		})
	})

	It("returns error on bad splunk host", func() {
		config.Host = ":"
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("protocol"))
	})

	It("Returns error on non-2xx response", func() {
		testServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
			writer.WriteHeader(500)
			writer.Write([]byte("Internal server error"))
		}))

		config.Host = testServer.URL
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("500"))
	})

	It("Returns error from http client", func() {
		config.Host = "foo://example.com"
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("foo"))
	})
})
<commit_msg>added a test for debug mode<commit_after>package eventwriter_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"

	"code.cloudfoundry.org/lager"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry-community/splunk-firehose-nozzle/eventwriter"
)

var _ = Describe("Splunk", func() {
	var (
		testServer      *httptest.Server
		capturedRequest *http.Request
		capturedBody    []byte
		splunkResponse  []byte
		logger          lager.Logger
		config          *SplunkConfig
	)

	BeforeEach(func() {
		logger = lager.NewLogger("test")
		config = &SplunkConfig{
			Token:   "token",
			Index:   "",
			Fields:  nil,
			SkipSSL: true,
			Logger:  logger,
		}
	})

	Context("success response", func() {
		BeforeEach(func() {
			capturedRequest = nil

			splunkResponse = []byte("{}")
			testServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
				capturedRequest = request
				body, err := ioutil.ReadAll(request.Body)
				if err != nil {
					panic(err)
				}
				capturedBody = body

				writer.Write(splunkResponse)
			}))

			config.Host = testServer.URL
		})

		AfterEach(func() {
			testServer.Close()
		})

		It("correctly authenticates requests", func() {
			tokenValue := "abc-some-random-token"
			config.Token = tokenValue

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			authValue := capturedRequest.Header.Get("Authorization")
			expectedAuthValue := fmt.Sprintf("Splunk %s", tokenValue)

			Expect(authValue).To(Equal(expectedAuthValue))
		})

		It("sets content type to json", func() {
			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			contentType := capturedRequest.Header.Get("Content-Type")
			Expect(contentType).To(Equal("application/json"))
		})

		It("sets app name to appName", func() {
			appName := "Splunk Firehose Nozzle"

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			applicationName := capturedRequest.Header.Get("__splunk_app_name")
			Expect(applicationName).To(Equal(appName))

		})

		It("sets app appVersion", func() {
			appVersion := "1.2.2"

			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			applicationVersion := capturedRequest.Header.Get("__splunk_app_version")
			Expect(applicationVersion).To(Equal(appVersion))

		})

		It("Writes batch event json", func() {
			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}
			event3 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello pluto",
			}}

			events := []map[string]interface{}{event1, event2, event3}
			err, sentCount := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())
			Expect(sentCount).To(Equal(uint64(3)))

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"}}

{"event":{"greeting":"hello mars"}}

{"event":{"greeting":"hello pluto"}}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))
		})

		It("sets index in splunk payload", func() {
			config.Index = "index_cf"
			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}

			events := []map[string]interface{}{event1, event2}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"},"index":"index_cf"}

{"event":{"greeting":"hello mars"},"index":"index_cf"}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))
		})

		It("adds fields to splunk palylaod", func() {
			fields := map[string]string{
				"foo":   "bar",
				"hello": "world",
			}
			config.Fields = fields

			client := NewSplunk(config)
			event1 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello world",
			}}
			event2 := map[string]interface{}{"event": map[string]interface{}{
				"greeting": "hello mars",
			}}

			events := []map[string]interface{}{event1, event2}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest).NotTo(BeNil())

			expectedPayload := strings.TrimSpace(`
{"event":{"greeting":"hello world"},"fields":{"foo":"bar","hello":"world"}}

{"event":{"greeting":"hello mars"},"fields":{"foo":"bar","hello":"world"}}
`)
			Expect(string(capturedBody)).To(Equal(expectedPayload))

		})

		It("Writes to correct endpoint", func() {
			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
			Expect(capturedRequest.URL.Path).To(Equal("/services/collector"))
		})

		It("Writes to stdout in debug without error", func() {
			config.Debug = true
			client := NewSplunk(config)
			events := []map[string]interface{}{}
			err, _ := client.Write(events)

			Expect(err).To(BeNil())
		})
	})

	It("returns error on bad splunk host", func() {
		config.Host = ":"
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("protocol"))
	})

	It("Returns error on non-2xx response", func() {
		testServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
			writer.WriteHeader(500)
			writer.Write([]byte("Internal server error"))
		}))

		config.Host = testServer.URL
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("500"))
	})

	It("Returns error from http client", func() {
		config.Host = "foo://example.com"
		client := NewSplunk(config)
		events := []map[string]interface{}{}
		err, _ := client.Write(events)

		Expect(err).NotTo(BeNil())
		Expect(err.Error()).To(ContainSubstring("foo"))
	})
})
<|endoftext|>


<commit_before>package uefi

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

	uuid "github.com/linuxboot/fiano/uuid"
)

// FirmwareVolume constants
const (
	FirmwareVolumeFixedHeaderSize  = 56
	FirmwareVolumeMinSize          = FirmwareVolumeFixedHeaderSize + 8 // +8 for the null block that terminates the block list
	FirmwareVolumeExtHeaderMinSize = 20
)

// FirmwareVolumeGUIDs maps the known FV GUIDs. These values come from
// uefi-firmware-parser
var FirmwareVolumeGUIDs = map[string]string{
	"7a9354d9-0468-444a-81ce-0bf617d890df": "FFS1",
	"8c8ce578-8a3d-4f1c-9935-896185c32dd3": "FFS2",
	"5473c07a-3dcb-4dca-bd6f-1e9689e7349a": "FFS3",
	"fff12b8d-7696-4c8b-a985-2747075b4f50": "NVRAM_EVSA",
	"cef5b9a3-476d-497f-9fdc-e98143e0422c": "NVRAM_NVAR",
	"00504624-8a59-4eeb-bd0f-6b36e96128e0": "NVRAM_EVSA2",
	"04adeead-61ff-4d31-b6ba-64f8bf901f5a": "APPLE_BOOT",
	"16b45da2-7d70-4aea-a58d-760e9ecb841d": "PFH1",
	"e360bdba-c3ce-46be-8f37-b231e5cb9f35": "PFH2",
}

// Block describes number and size of the firmware volume blocks
type Block struct {
	Count uint32
	Size  uint32
}

// FirmwareVolumeFixedHeader contains the fixed fields of a firmware volume
// header
type FirmwareVolumeFixedHeader struct {
	_               [16]uint8
	FileSystemGUID  uuid.UUID
	Length          uint64
	Signature       uint32
	Attributes      uint32 // UEFI PI spec volume 3.2.1 EFI_FIRMWARE_VOLUME_HEADER
	HeaderLen       uint16
	Checksum        uint16
	ExtHeaderOffset uint16
	Reserved        uint8 `json:"-"`
	Revision        uint8
	// _               [3]uint8
}

// FirmwareVolumeExtHeader contains the fields of an extended firmware volume
// header
type FirmwareVolumeExtHeader struct {
	FVName        uuid.UUID
	ExtHeaderSize uint32
}

// FirmwareVolume represents a firmware volume. It combines the fixed header and
// a variable list of blocks
type FirmwareVolume struct {
	FirmwareVolumeFixedHeader
	// there must be at least one that is zeroed and indicates the end of the
	// block list
	// We don't really have to care about blocks because we just read everything in.
	Blocks []Block
	FirmwareVolumeExtHeader
	Files []FirmwareFile

	// Variables not in the binary for us to keep track of stuff/print
	DataOffset uint64
	guidString string
	guidName   string
	buf        []byte
}

// FindFirmwareVolumeOffset searches for a firmware volume signature, "_FVH"
// using 8-byte alignment. If found, returns the offset from the start of the
// bios region, otherwise returns -1.
func FindFirmwareVolumeOffset(data []byte) int64 {
	if len(data) < 32 {
		return -1
	}
	var (
		offset int64
		fvSig  = []byte("_FVH")
	)
	for offset = 32; offset < int64(len(data)); offset += 8 {
		if bytes.Equal(data[offset:offset+4], fvSig) {
			return offset - 40 // the actual volume starts 40 bytes before the signature
		}
	}
	return -1
}

func align8(val uint64) uint64 {
	return (val + 7) & ^uint64(7)
}

// NewFirmwareVolume parses a sequence of bytes and returns a FirmwareVolume
// object, if a valid one is passed, or an error
func NewFirmwareVolume(data []byte) (*FirmwareVolume, error) {
	var fv FirmwareVolume

	if len(data) < FirmwareVolumeMinSize {
		return nil, fmt.Errorf("Firmware Volume size too small: expected %v bytes, got %v",
			FirmwareVolumeMinSize,
			len(data),
		)
	}
	reader := bytes.NewReader(data)
	if err := binary.Read(reader, binary.LittleEndian, &fv.FirmwareVolumeFixedHeader); err != nil {
		return nil, err
	}
	// read the block map
	blocks := make([]Block, 0)
	for {
		var block Block
		if err := binary.Read(reader, binary.LittleEndian, &block); err != nil {
			return nil, err
		}
		if block.Count == 0 && block.Size == 0 {
			// found the terminating block
			break
		}
		blocks = append(blocks, block)
	}
	fv.Blocks = blocks

	// Parse the extended header and figure out the start of data
	fv.DataOffset = uint64(fv.HeaderLen)
	if fv.ExtHeaderOffset != 0 && uint64(fv.ExtHeaderOffset) < fv.Length-FirmwareVolumeExtHeaderMinSize {
		// jump to ext header offset.
		r := bytes.NewReader(data[fv.ExtHeaderOffset:])
		if err := binary.Read(r, binary.LittleEndian, &fv.FirmwareVolumeExtHeader); err != nil {
			return nil, fmt.Errorf("unable to parse FV extended header, got: %v", err)
		}
		// TODO: will the ext header ever end before the regular header? I don't believe so. Add a check?
		fv.DataOffset = uint64(fv.ExtHeaderOffset) + uint64(fv.ExtHeaderSize)
	}
	// Make sure DataOffset is 8 byte aligned at least.
	// TODO: handle alignment field in header.
	fv.DataOffset = align8(fv.DataOffset)

	var ok bool
	fv.guidString = fv.FileSystemGUID.String()
	fv.guidName, ok = FirmwareVolumeGUIDs[fv.guidString]
	if !ok {
		fv.guidName = "Unknown"
	}

	// Parse the files.
	// TODO: handle fv data alignment.
	// Start from the end of the fv header.
	lh := fv.Length - FileHeaderMinLength
	for offset, prevLen := fv.DataOffset, uint64(0); offset < lh; offset += prevLen {
		offset = align8(offset)
		file, err := NewFirmwareFile(data[offset:])
		if err != nil {
			return nil, fmt.Errorf("unable to construct firmware file at offset %#x into FV: %v", offset, err)
		}
		if file == nil {
			// We've reached free space. Terminate
			break
		}
		fv.Files = append(fv.Files, *file)
		prevLen = file.Header.ExtendedSize
	}

	// slice the buffer
	fv.buf = data[:fv.Length]
	return &fv, nil
}
<commit_msg>Fix FV name parsing<commit_after>package uefi

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

	uuid "github.com/linuxboot/fiano/uuid"
)

// FirmwareVolume constants
const (
	FirmwareVolumeFixedHeaderSize  = 56
	FirmwareVolumeMinSize          = FirmwareVolumeFixedHeaderSize + 8 // +8 for the null block that terminates the block list
	FirmwareVolumeExtHeaderMinSize = 20
)

// FVGUIDs holds common FV type names
var FVGUIDs map[uuid.UUID]string

// Valid FV GUIDs
var (
	FFS1      *uuid.UUID
	FFS2      *uuid.UUID
	FFS3      *uuid.UUID
	EVSA      *uuid.UUID
	NVAR      *uuid.UUID
	EVSA2     *uuid.UUID
	AppleBoot *uuid.UUID
	PFH1      *uuid.UUID
	PFH2      *uuid.UUID
)

func init() {
	FFS1, _ = uuid.Parse("7a9354d9-0468-444a-81ce-0bf617d890df")
	FFS2, _ = uuid.Parse("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
	FFS3, _ = uuid.Parse("5473c07a-3dcb-4dca-bd6f-1e9689e7349a")
	EVSA, _ = uuid.Parse("fff12b8d-7696-4c8b-a985-2747075b4f50")
	NVAR, _ = uuid.Parse("cef5b9a3-476d-497f-9fdc-e98143e0422c")
	EVSA2, _ = uuid.Parse("00504624-8a59-4eeb-bd0f-6b36e96128e0")
	AppleBoot, _ = uuid.Parse("04adeead-61ff-4d31-b6ba-64f8bf901f5a")
	PFH1, _ = uuid.Parse("16b45da2-7d70-4aea-a58d-760e9ecb841d")
	PFH2, _ = uuid.Parse("e360bdba-c3ce-46be-8f37-b231e5cb9f35")

	// Add names to map
	FVGUIDs = make(map[uuid.UUID]string)
	FVGUIDs[*FFS1] = "FFS1"
	FVGUIDs[*FFS2] = "FFS2"
	FVGUIDs[*FFS3] = "FFS3"
	FVGUIDs[*EVSA] = "NVRAM_EVSA"
	FVGUIDs[*NVAR] = "NVRAM_NVAR"
	FVGUIDs[*EVSA2] = "NVRAM_EVSA2"
	FVGUIDs[*AppleBoot] = "APPLE_BOOT"
	FVGUIDs[*PFH1] = "PFH1"
	FVGUIDs[*PFH2] = "PFH2"
}

// Block describes number and size of the firmware volume blocks
type Block struct {
	Count uint32
	Size  uint32
}

// FirmwareVolumeFixedHeader contains the fixed fields of a firmware volume
// header
type FirmwareVolumeFixedHeader struct {
	_               [16]uint8
	FileSystemGUID  uuid.UUID
	Length          uint64
	Signature       uint32
	Attributes      uint32 // UEFI PI spec volume 3.2.1 EFI_FIRMWARE_VOLUME_HEADER
	HeaderLen       uint16
	Checksum        uint16
	ExtHeaderOffset uint16
	Reserved        uint8 `json:"-"`
	Revision        uint8
	// _               [3]uint8
}

// FirmwareVolumeExtHeader contains the fields of an extended firmware volume
// header
type FirmwareVolumeExtHeader struct {
	FVName        uuid.UUID
	ExtHeaderSize uint32
}

// FirmwareVolume represents a firmware volume. It combines the fixed header and
// a variable list of blocks
type FirmwareVolume struct {
	FirmwareVolumeFixedHeader
	// there must be at least one that is zeroed and indicates the end of the
	// block list
	// We don't really have to care about blocks because we just read everything in.
	Blocks []Block
	FirmwareVolumeExtHeader
	Files []FirmwareFile

	// Variables not in the binary for us to keep track of stuff/print
	DataOffset uint64
	fvType     string
	buf        []byte
}

// FindFirmwareVolumeOffset searches for a firmware volume signature, "_FVH"
// using 8-byte alignment. If found, returns the offset from the start of the
// bios region, otherwise returns -1.
func FindFirmwareVolumeOffset(data []byte) int64 {
	if len(data) < 32 {
		return -1
	}
	var (
		offset int64
		fvSig  = []byte("_FVH")
	)
	for offset = 32; offset < int64(len(data)); offset += 8 {
		if bytes.Equal(data[offset:offset+4], fvSig) {
			return offset - 40 // the actual volume starts 40 bytes before the signature
		}
	}
	return -1
}

func align8(val uint64) uint64 {
	return (val + 7) & ^uint64(7)
}

// NewFirmwareVolume parses a sequence of bytes and returns a FirmwareVolume
// object, if a valid one is passed, or an error
func NewFirmwareVolume(data []byte) (*FirmwareVolume, error) {
	var fv FirmwareVolume

	if len(data) < FirmwareVolumeMinSize {
		return nil, fmt.Errorf("Firmware Volume size too small: expected %v bytes, got %v",
			FirmwareVolumeMinSize,
			len(data),
		)
	}
	reader := bytes.NewReader(data)
	if err := binary.Read(reader, binary.LittleEndian, &fv.FirmwareVolumeFixedHeader); err != nil {
		return nil, err
	}
	// read the block map
	blocks := make([]Block, 0)
	for {
		var block Block
		if err := binary.Read(reader, binary.LittleEndian, &block); err != nil {
			return nil, err
		}
		if block.Count == 0 && block.Size == 0 {
			// found the terminating block
			break
		}
		blocks = append(blocks, block)
	}
	fv.Blocks = blocks

	// Parse the extended header and figure out the start of data
	fv.DataOffset = uint64(fv.HeaderLen)
	if fv.ExtHeaderOffset != 0 && uint64(fv.ExtHeaderOffset) < fv.Length-FirmwareVolumeExtHeaderMinSize {
		// jump to ext header offset.
		r := bytes.NewReader(data[fv.ExtHeaderOffset:])
		if err := binary.Read(r, binary.LittleEndian, &fv.FirmwareVolumeExtHeader); err != nil {
			return nil, fmt.Errorf("unable to parse FV extended header, got: %v", err)
		}
		// TODO: will the ext header ever end before the regular header? I don't believe so. Add a check?
		fv.DataOffset = uint64(fv.ExtHeaderOffset) + uint64(fv.ExtHeaderSize)
	}
	// Make sure DataOffset is 8 byte aligned at least.
	// TODO: handle alignment field in header.
	fv.DataOffset = align8(fv.DataOffset)

	fv.fvType = FVGUIDs[fv.FileSystemGUID]

	// Parse the files.
	// TODO: handle fv data alignment.
	// Start from the end of the fv header.
	lh := fv.Length - FileHeaderMinLength
	for offset, prevLen := fv.DataOffset, uint64(0); offset < lh; offset += prevLen {
		offset = align8(offset)
		file, err := NewFirmwareFile(data[offset:])
		if err != nil {
			return nil, fmt.Errorf("unable to construct firmware file at offset %#x into FV: %v", offset, err)
		}
		if file == nil {
			// We've reached free space. Terminate
			break
		}
		fv.Files = append(fv.Files, *file)
		prevLen = file.Header.ExtendedSize
	}

	// slice the buffer
	fv.buf = data[:fv.Length]
	return &fv, nil
}
<|endoftext|>


<commit_before>// run

// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
)

func main() {
	if runtime.Compiler != "gc" || runtime.GOOS == "nacl" {
		return
	}
	a, err := build.ArchChar(runtime.GOARCH)
	if err != nil {
		fmt.Println("BUG:", err)
		os.Exit(1)
	}
	out := run("go", "tool", a+"g", "-S", filepath.Join("fixedbugs", "issue9355.dir", "a.go"))
	patterns := []string{
		`rel 0\+\d t=1 \"\"\.x\+8\r?\n`,  // y = &x.b
		`rel 0\+\d t=1 \"\"\.x\+28\r?\n`, // z = &x.d.q
		`rel 0\+\d t=1 \"\"\.b\+5\r?\n`,  // c = &b[5]
		`rel 0\+\d t=1 \"\"\.x\+88\r?\n`, // w = &x.f[3].r
	}
	for _, p := range patterns {
		if ok, err := regexp.Match(p, out); !ok || err != nil {
			println(string(out))
			panic("can't find pattern " + p)
		}
	}
}

func run(cmd string, args ...string) []byte {
	out, err := exec.Command(cmd, args...).CombinedOutput()
	if err != nil {
		fmt.Println(string(out))
		fmt.Println(err)
		os.Exit(1)
	}
	return out
}
<commit_msg>test/fixedbugs/issue9355: fix build on arm and power64<commit_after>// run

// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
)

func main() {
	if runtime.Compiler != "gc" || runtime.GOOS == "nacl" {
		return
	}
	a, err := build.ArchChar(runtime.GOARCH)
	if err != nil {
		fmt.Println("BUG:", err)
		os.Exit(1)
	}
	out := run("go", "tool", a+"g", "-S", filepath.Join("fixedbugs", "issue9355.dir", "a.go"))
	// 6g/8g print the offset as dec, but 5g/9g print the offset as hex.
	patterns := []string{
		`rel 0\+\d t=1 \"\"\.x\+8\r?\n`,       // y = &x.b
		`rel 0\+\d t=1 \"\"\.x\+(28|1c)\r?\n`, // z = &x.d.q
		`rel 0\+\d t=1 \"\"\.b\+5\r?\n`,       // c = &b[5]
		`rel 0\+\d t=1 \"\"\.x\+(88|58)\r?\n`, // w = &x.f[3].r
	}
	for _, p := range patterns {
		if ok, err := regexp.Match(p, out); !ok || err != nil {
			println(string(out))
			panic("can't find pattern " + p)
		}
	}
}

func run(cmd string, args ...string) []byte {
	out, err := exec.Command(cmd, args...).CombinedOutput()
	if err != nil {
		fmt.Println(string(out))
		fmt.Println(err)
		os.Exit(1)
	}
	return out
}
<|endoftext|>


<commit_before>package cloudwatch

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
)

type RejectedLogEventsInfoError struct {
	Info *cloudwatchlogs.RejectedLogEventsInfo
}

func (e *RejectedLogEventsInfoError) Error() string {
	return fmt.Sprintf("log messages were rejected")
}

// Writer is an io.Writer implementation that writes lines to a cloudwatch logs
// stream.
//
// The writer will only flush log events every 1 second or when the buffer
// becomes full according to the following from http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
//
//	 The maximum batch size is 1,048,576 bytes, and this size is calculated
//	 as the sum of all event messages in UTF-8, plus 26 bytes for each log
//	 event.
type Writer struct {
	group, stream, sequenceToken *string

	client client

	closed bool
	err    error

	events eventsBuffer

	throttle <-chan time.Time

	sync.Mutex // This protects calls to flush.
}

func NewWriter(group, stream string, client *cloudwatchlogs.CloudWatchLogs) *Writer {
	w := &Writer{
		group:    aws.String(group),
		stream:   aws.String(stream),
		client:   client,
		throttle: time.Tick(writeThrottle),
	}
	go w.start() // start flushing
	return w
}

// Write takes b, and creates cloudwatch log events for each individual line.
// If Flush returns an error, subsequent calls to Write will fail.
func (w *Writer) Write(b []byte) (int, error) {
	if w.closed {
		return 0, io.ErrClosedPipe
	}

	if w.err != nil {
		return 0, w.err
	}

	return w.buffer(b)
}

// starts continously flushing the buffered events.
func (w *Writer) start() error {
	for {
		// Exit if the stream is closed.
		if w.closed {
			return nil
		}

		<-w.throttle
		if err := w.Flush(); err != nil {
			return err
		}
	}
}

// Closes the writer. Any subsequent calls to Write will return
// io.ErrClosedPipe.
func (w *Writer) Close() error {
	w.closed = true
	return w.Flush() // Flush remaining buffer.
}

// Flush flushes the events that are currently buffered.
func (w *Writer) Flush() error {
	w.Lock()
	defer w.Unlock()

	events := w.events.drain()

	// No events to flush.
	if len(events) == 0 {
		return nil
	}

	w.err = w.flush(events)
	return w.err
}

// flush flashes a slice of log events. This method should be called
// sequentially to ensure that the sequence token is updated properly.
func (w *Writer) flush(events []*cloudwatchlogs.InputLogEvent) error {
	resp, err := w.client.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
		LogEvents:     events,
		LogGroupName:  w.group,
		LogStreamName: w.stream,
		SequenceToken: w.sequenceToken,
	})
	if err != nil {
		return err
	}

	if resp.RejectedLogEventsInfo != nil {
		w.err = &RejectedLogEventsInfoError{Info: resp.RejectedLogEventsInfo}
		return w.err
	}

	w.sequenceToken = resp.NextSequenceToken

	return nil
}

// buffer splits up b into individual log events and inserts them into the
// buffer.
func (w *Writer) buffer(b []byte) (int, error) {
	r := bufio.NewReader(bytes.NewReader(b))

	var (
		n   int
		eof bool
	)

	for !eof {
		b, err := r.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				eof = true
			} else {
				break
			}
		}

		if len(b) == 0 {
			continue
		}

		w.events.add(&cloudwatchlogs.InputLogEvent{
			Message:   aws.String(string(b)),
			Timestamp: aws.Int64(now().UnixNano() / 1000000),
		})

		n += len(b)
	}

	return n, nil
}

// eventsBuffer represents a buffer of cloudwatch events that are protected by a
// mutex.
type eventsBuffer struct {
	sync.Mutex
	events []*cloudwatchlogs.InputLogEvent
}

func (b *eventsBuffer) add(event *cloudwatchlogs.InputLogEvent) {
	b.Lock()
	defer b.Unlock()

	b.events = append(b.events, event)
}

func (b *eventsBuffer) drain() []*cloudwatchlogs.InputLogEvent {
	b.Lock()
	defer b.Unlock()

	events := b.events[:]
	b.events = nil
	return events
}
<commit_msg>Remove bad comment.<commit_after>package cloudwatch

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
)

type RejectedLogEventsInfoError struct {
	Info *cloudwatchlogs.RejectedLogEventsInfo
}

func (e *RejectedLogEventsInfoError) Error() string {
	return fmt.Sprintf("log messages were rejected")
}

// Writer is an io.Writer implementation that writes lines to a cloudwatch logs
// stream.
type Writer struct {
	group, stream, sequenceToken *string

	client client

	closed bool
	err    error

	events eventsBuffer

	throttle <-chan time.Time

	sync.Mutex // This protects calls to flush.
}

func NewWriter(group, stream string, client *cloudwatchlogs.CloudWatchLogs) *Writer {
	w := &Writer{
		group:    aws.String(group),
		stream:   aws.String(stream),
		client:   client,
		throttle: time.Tick(writeThrottle),
	}
	go w.start() // start flushing
	return w
}

// Write takes b, and creates cloudwatch log events for each individual line.
// If Flush returns an error, subsequent calls to Write will fail.
func (w *Writer) Write(b []byte) (int, error) {
	if w.closed {
		return 0, io.ErrClosedPipe
	}

	if w.err != nil {
		return 0, w.err
	}

	return w.buffer(b)
}

// starts continously flushing the buffered events.
func (w *Writer) start() error {
	for {
		// Exit if the stream is closed.
		if w.closed {
			return nil
		}

		<-w.throttle
		if err := w.Flush(); err != nil {
			return err
		}
	}
}

// Closes the writer. Any subsequent calls to Write will return
// io.ErrClosedPipe.
func (w *Writer) Close() error {
	w.closed = true
	return w.Flush() // Flush remaining buffer.
}

// Flush flushes the events that are currently buffered.
func (w *Writer) Flush() error {
	w.Lock()
	defer w.Unlock()

	events := w.events.drain()

	// No events to flush.
	if len(events) == 0 {
		return nil
	}

	w.err = w.flush(events)
	return w.err
}

// flush flashes a slice of log events. This method should be called
// sequentially to ensure that the sequence token is updated properly.
func (w *Writer) flush(events []*cloudwatchlogs.InputLogEvent) error {
	resp, err := w.client.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
		LogEvents:     events,
		LogGroupName:  w.group,
		LogStreamName: w.stream,
		SequenceToken: w.sequenceToken,
	})
	if err != nil {
		return err
	}

	if resp.RejectedLogEventsInfo != nil {
		w.err = &RejectedLogEventsInfoError{Info: resp.RejectedLogEventsInfo}
		return w.err
	}

	w.sequenceToken = resp.NextSequenceToken

	return nil
}

// buffer splits up b into individual log events and inserts them into the
// buffer.
func (w *Writer) buffer(b []byte) (int, error) {
	r := bufio.NewReader(bytes.NewReader(b))

	var (
		n   int
		eof bool
	)

	for !eof {
		b, err := r.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				eof = true
			} else {
				break
			}
		}

		if len(b) == 0 {
			continue
		}

		w.events.add(&cloudwatchlogs.InputLogEvent{
			Message:   aws.String(string(b)),
			Timestamp: aws.Int64(now().UnixNano() / 1000000),
		})

		n += len(b)
	}

	return n, nil
}

// eventsBuffer represents a buffer of cloudwatch events that are protected by a
// mutex.
type eventsBuffer struct {
	sync.Mutex
	events []*cloudwatchlogs.InputLogEvent
}

func (b *eventsBuffer) add(event *cloudwatchlogs.InputLogEvent) {
	b.Lock()
	defer b.Unlock()

	b.events = append(b.events, event)
}

func (b *eventsBuffer) drain() []*cloudwatchlogs.InputLogEvent {
	b.Lock()
	defer b.Unlock()

	events := b.events[:]
	b.events = nil
	return events
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package syslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"
)

// tagOffset represents the substring start value for the tag to return
// the logfileName value from the syslogtag.  Substrings in syslog are
// indexed from 1, hence the + 1.
const tagOffset = len("juju-") + 1

// The rsyslog conf for state server nodes.
// Messages are gathered from other nodes and accumulated in an all-machines.log file.
//
// The apparmor profile is quite strict about where rsyslog can write files.
// Instead of poking with the profile, the local provider now logs to
// {{logDir}}-{{user}}-{{env name}}/all-machines.log, and a symlink is made
// in the local provider log dir to point to that file. The file is also
// created with 0644 so the user can read it without poking permissions. By
// default rsyslog creates files with 0644, but in the ubuntu package, the
// setting is changed to 0640, which means normal users can't read the log
// file. Using a new action directive (new as in not-legacy), we can specify
// the file create mode so it doesn't use the default.
//
// I would dearly love to write the filtering action as follows to avoid setting
// and resetting the global $FileCreateMode, but alas, precise doesn't support it
//
// if $syslogtag startswith "juju{{namespace}}-" then
//   action(type="omfile"
//          File="{{logDir}}{{namespace}}/all-machines.log"
//          Template="JujuLogFormat{{namespace}}"
//          FileCreateMode="0644")
// & stop
//
// Instead we need to mess with the global FileCreateMode.  We set it back
// to the ubuntu default after defining our rule.
const stateServerRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$ModLoad imtcp
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$DefaultNetstreamDriverCertFile {{tlsCertPath}}
$DefaultNetstreamDriverKeyFile {{tlsKeyPath}}
$InputTCPServerStreamDriverAuthMode anon
$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode
$InputTCPServerRun {{portNumber}}

# Messages received from remote rsyslog machines have messages prefixed with a space,
# so add one in for local messages too if needed.
$template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"

$FileCreateMode 0644
:syslogtag, startswith, "juju{{namespace}}-" {{logDir}}/all-machines.log;JujuLogFormat{{namespace}}
& ~
$FileCreateMode 0640
`

// The rsyslog conf for non-state server nodes.
// Messages are forwarded to the state server node.
const nodeRsyslogTemplate = `
$ModLoad imfile

# Enable reliable forwarding.
$ActionQueueType LinkedList
$ActionQueueFileName {{logfileName}}{{namespace}}
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$ActionSendStreamDriverAuthMode anon
$ActionSendStreamDriverMode 1 # run driver in TLS-only mode

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"

:syslogtag, startswith, "juju{{namespace}}-" {{range $i, $bootstrapIP := bootstrapHosts}}{{if $i}} & {{end}}@@{{$bootstrapIP}}:{{portNumber}};LongTagForwardFormat{{end}}
& ~
`

// nodeRsyslogTemplateTLSHeader is prepended to
// nodeRsyslogTemplate if TLS is to be used.
const nodeRsyslogTemplateTLSHeader = `
`

const (
	defaultConfigDir          = "/etc/rsyslog.d"
	defaultCACertFileName     = "ca-cert.pem"
	defaultServerCertFileName = "rsyslog-cert.pem"
	defaultServerKeyFileName  = "rsyslog-key.pem"
)

// SyslogConfigRenderer instances are used to generate a rsyslog conf file.
type SyslogConfigRenderer interface {
	Render() ([]byte, error)
}

// SyslogConfig provides a means to configure and generate rsyslog conf files for
// the state server nodes and unit nodes.
// rsyslog is configured to tail the specified log file.
type SyslogConfig struct {
	// the template representing the config file contents.
	configTemplate string
	// the directory where the config file is written.
	ConfigDir string
	// the config file name.
	ConfigFileName string
	// the name of the log file to tail.
	LogFileName string
	// the addresses of the state server to which messages should be forwarded.
	StateServerAddresses []string
	// CA certificate file name.
	CACertFileName string
	// Server certificate file name.
	ServerCertFileName string
	// Server private key file name.
	ServerKeyFileName string
	// the port number for the listener
	Port int
	// the directory for the logfiles
	LogDir string
	// namespace is used when there are multiple environments on one machine
	Namespace string
}

// NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
// to the state server nodes.
func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       nodeRsyslogTemplate,
		StateServerAddresses: stateServerAddresses,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               logDir,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

// NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
// various unit nodes.
func NewAccumulateConfig(logFile, logDir string, port int, namespace string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate: stateServerRsyslogTemplate,
		LogFileName:    logFile,
		Port:           port,
		LogDir:         logDir,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

func either(a, b string) string {
	if a != "" {
		return a
	}
	return b
}

func (slConfig *SyslogConfig) ConfigFilePath() string {
	dir := either(slConfig.ConfigDir, defaultConfigDir)
	return filepath.Join(dir, slConfig.ConfigFileName)
}

func (slConfig *SyslogConfig) CACertPath() string {
	filename := either(slConfig.CACertFileName, defaultCACertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerCertPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerCertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerKeyPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

// Render generates the rsyslog config.
func (slConfig *SyslogConfig) Render() ([]byte, error) {
	var bootstrapHosts = func() []string {
		var hosts []string
		for _, addr := range slConfig.StateServerAddresses {
			parts := strings.Split(addr, ":")
			hosts = append(hosts, parts[0])
		}
		return hosts
	}

	var logFilePath = func() string {
		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
	}

	t := template.New("")
	t.Funcs(template.FuncMap{
		"logfileName":    func() string { return slConfig.LogFileName },
		"bootstrapHosts": bootstrapHosts,
		"logfilePath":    logFilePath,
		"portNumber":     func() int { return slConfig.Port },
		"logDir":         func() string { return slConfig.LogDir },
		"namespace":      func() string { return slConfig.Namespace },
		"tagStart":       func() int { return tagOffset + len(slConfig.Namespace) },
		"tlsCACertPath":  slConfig.CACertPath,
		"tlsCertPath":    slConfig.ServerCertPath,
		"tlsKeyPath":     slConfig.ServerKeyPath,
	})

	// Process the rsyslog config template and echo to the conf file.
	p, err := t.Parse(slConfig.configTemplate)
	if err != nil {
		return nil, err
	}
	var confBuf bytes.Buffer
	if err := p.Execute(&confBuf, nil); err != nil {
		return nil, err
	}
	return confBuf.Bytes(), nil
}

// Write generates and writes the rsyslog config.
func (slConfig *SyslogConfig) Write() error {
	data, err := slConfig.Render()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
	return err
}
<commit_msg>utils/syslog: adjusted rsyslog node config<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package syslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"
)

// tagOffset represents the substring start value for the tag to return
// the logfileName value from the syslogtag.  Substrings in syslog are
// indexed from 1, hence the + 1.
const tagOffset = len("juju-") + 1

// The rsyslog conf for state server nodes.
// Messages are gathered from other nodes and accumulated in an all-machines.log file.
//
// The apparmor profile is quite strict about where rsyslog can write files.
// Instead of poking with the profile, the local provider now logs to
// {{logDir}}-{{user}}-{{env name}}/all-machines.log, and a symlink is made
// in the local provider log dir to point to that file. The file is also
// created with 0644 so the user can read it without poking permissions. By
// default rsyslog creates files with 0644, but in the ubuntu package, the
// setting is changed to 0640, which means normal users can't read the log
// file. Using a new action directive (new as in not-legacy), we can specify
// the file create mode so it doesn't use the default.
//
// I would dearly love to write the filtering action as follows to avoid setting
// and resetting the global $FileCreateMode, but alas, precise doesn't support it
//
// if $syslogtag startswith "juju{{namespace}}-" then
//   action(type="omfile"
//          File="{{logDir}}{{namespace}}/all-machines.log"
//          Template="JujuLogFormat{{namespace}}"
//          FileCreateMode="0644")
// & stop
//
// Instead we need to mess with the global FileCreateMode.  We set it back
// to the ubuntu default after defining our rule.
const stateServerRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$ModLoad imtcp
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$DefaultNetstreamDriverCertFile {{tlsCertPath}}
$DefaultNetstreamDriverKeyFile {{tlsKeyPath}}
$InputTCPServerStreamDriverAuthMode anon
$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode
$InputTCPServerRun {{portNumber}}

# Messages received from remote rsyslog machines have messages prefixed with a space,
# so add one in for local messages too if needed.
$template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"

$FileCreateMode 0644
:syslogtag, startswith, "juju{{namespace}}-" {{logDir}}/all-machines.log;JujuLogFormat{{namespace}}
& ~
$FileCreateMode 0640
`

// The rsyslog conf for non-state server nodes.
// Messages are forwarded to the state server node.
const nodeRsyslogTemplate = `
$ModLoad imfile

{{range $i, $bootstrapIP := bootstrapHosts}}
{{if $i}}

{{end}}
# Enable reliable forwarding.
$ActionQueueType LinkedList
$ActionQueueFileName {{logfileName}}{{namespace}}
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$ActionSendStreamDriverAuthMode anon
$ActionSendStreamDriverMode 1 # run driver in TLS-only mode

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"

:syslogtag, startswith, "juju{{namespace}}-" @@{{$bootstrapIP}}:{{portNumber}};LongTagForwardFormat
{{end}}
& ~
`

// nodeRsyslogTemplateTLSHeader is prepended to
// nodeRsyslogTemplate if TLS is to be used.
const nodeRsyslogTemplateTLSHeader = `
`

const (
	defaultConfigDir          = "/etc/rsyslog.d"
	defaultCACertFileName     = "ca-cert.pem"
	defaultServerCertFileName = "rsyslog-cert.pem"
	defaultServerKeyFileName  = "rsyslog-key.pem"
)

// SyslogConfigRenderer instances are used to generate a rsyslog conf file.
type SyslogConfigRenderer interface {
	Render() ([]byte, error)
}

// SyslogConfig provides a means to configure and generate rsyslog conf files for
// the state server nodes and unit nodes.
// rsyslog is configured to tail the specified log file.
type SyslogConfig struct {
	// the template representing the config file contents.
	configTemplate string
	// the directory where the config file is written.
	ConfigDir string
	// the config file name.
	ConfigFileName string
	// the name of the log file to tail.
	LogFileName string
	// the addresses of the state server to which messages should be forwarded.
	StateServerAddresses []string
	// CA certificate file name.
	CACertFileName string
	// Server certificate file name.
	ServerCertFileName string
	// Server private key file name.
	ServerKeyFileName string
	// the port number for the listener
	Port int
	// the directory for the logfiles
	LogDir string
	// namespace is used when there are multiple environments on one machine
	Namespace string
}

// NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
// to the state server nodes.
func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       nodeRsyslogTemplate,
		StateServerAddresses: stateServerAddresses,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               logDir,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

// NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
// various unit nodes.
func NewAccumulateConfig(logFile, logDir string, port int, namespace string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate: stateServerRsyslogTemplate,
		LogFileName:    logFile,
		Port:           port,
		LogDir:         logDir,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

func either(a, b string) string {
	if a != "" {
		return a
	}
	return b
}

func (slConfig *SyslogConfig) ConfigFilePath() string {
	dir := either(slConfig.ConfigDir, defaultConfigDir)
	return filepath.Join(dir, slConfig.ConfigFileName)
}

func (slConfig *SyslogConfig) CACertPath() string {
	filename := either(slConfig.CACertFileName, defaultCACertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerCertPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerCertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerKeyPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

// Render generates the rsyslog config.
func (slConfig *SyslogConfig) Render() ([]byte, error) {
	var bootstrapHosts = func() []string {
		var hosts []string
		for _, addr := range slConfig.StateServerAddresses {
			parts := strings.Split(addr, ":")
			hosts = append(hosts, parts[0])
		}
		return hosts
	}

	var logFilePath = func() string {
		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
	}

	t := template.New("")
	t.Funcs(template.FuncMap{
		"logfileName":    func() string { return slConfig.LogFileName },
		"bootstrapHosts": bootstrapHosts,
		"logfilePath":    logFilePath,
		"portNumber":     func() int { return slConfig.Port },
		"logDir":         func() string { return slConfig.LogDir },
		"namespace":      func() string { return slConfig.Namespace },
		"tagStart":       func() int { return tagOffset + len(slConfig.Namespace) },
		"tlsCACertPath":  slConfig.CACertPath,
		"tlsCertPath":    slConfig.ServerCertPath,
		"tlsKeyPath":     slConfig.ServerKeyPath,
	})

	// Process the rsyslog config template and echo to the conf file.
	p, err := t.Parse(slConfig.configTemplate)
	if err != nil {
		return nil, err
	}
	var confBuf bytes.Buffer
	if err := p.Execute(&confBuf, nil); err != nil {
		return nil, err
	}
	return confBuf.Bytes(), nil
}

// Write generates and writes the rsyslog config.
func (slConfig *SyslogConfig) Write() error {
	data, err := slConfig.Render()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
	return err
}
<|endoftext|>


<commit_before>// Copyright 2015 PingCAP, 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 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package memkv

import (
	. "github.com/pingcap/check"
	"github.com/pingcap/tidb/util/types"
)

var _ = Suite(&testBtreeSuite{})

type testBtreeSuite struct {
}

func (*testBtreeSuite) TestBtree(c *C) {
	t := NewTree(types.Collators[true])
	c.Assert(t, NotNil)
	// Insert
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		v := []interface{}{string(i + 1)}
		t.Set(k, v)
	}
	for i := 0; i < 102400; i++ {
		k := []interface{}{i}
		v := []interface{}{string(i)}
		t.Set(k, v)
	}
	// Delete
	for i := 512; i < 102400; i++ {
		k := []interface{}{i}
		t.Delete(k)
	}
	// Get
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		v, ok := t.Get(k)
		c.Assert(ok, IsTrue)
		c.Assert(v, HasLen, 1)
		c.Assert(v[0], Equals, string(i))
	}
	// Get unexists key
	for i := 512; i < 102400; i++ {
		k := []interface{}{i}
		v, ok := t.Get(k)
		c.Assert(ok, IsFalse)
		c.Assert(v, IsNil)
	}
	// First
	k, v := t.First()
	c.Assert(k, NotNil)
	c.Assert(v, NotNil)
	c.Assert(k[0], Equals, 0)

	// Seek
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		e, ok := t.Seek(k)
		c.Assert(ok, IsTrue)
		c.Assert(e, NotNil)
		c.Assert(e.k[0], Equals, i)
		c.Assert(e.q.d[e.i].v[0], Equals, string(i))

		pk, pv, err := e.Prev()
		c.Assert(err, IsNil)
		c.Assert(pk, NotNil)
		c.Assert(pv, NotNil)
		c.Assert(pk[0], Equals, i)
		c.Assert(pv[0], Equals, string(i))
		pk, pv, err = e.Prev()
		if i > 0 {
			c.Assert(err, IsNil)
			c.Assert(pk, NotNil)
			c.Assert(pv, NotNil)
			c.Assert(pk[0], Equals, i-1)
			c.Assert(pv[0], Equals, string(i-1))
		}
	}

	t.Clear()
	e, err := t.SeekLast()
	c.Assert(e, IsNil)
	c.Assert(err, NotNil)
}
<commit_msg>memkv: reduce test time<commit_after>// Copyright 2015 PingCAP, 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 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package memkv

import (
	. "github.com/pingcap/check"
	"github.com/pingcap/tidb/util/types"
)

var _ = Suite(&testBtreeSuite{})

type testBtreeSuite struct {
}

func (*testBtreeSuite) TestBtree(c *C) {
	t := NewTree(types.Collators[true])
	c.Assert(t, NotNil)
	// Insert
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		v := []interface{}{string(i + 1)}
		t.Set(k, v)
	}
	for i := 0; i < 1024; i++ {
		k := []interface{}{i}
		v := []interface{}{string(i)}
		t.Set(k, v)
	}
	// Delete
	for i := 512; i < 1024; i++ {
		k := []interface{}{i}
		t.Delete(k)
	}
	// Get
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		v, ok := t.Get(k)
		c.Assert(ok, IsTrue)
		c.Assert(v, HasLen, 1)
		c.Assert(v[0], Equals, string(i))
	}
	// Get unexists key
	for i := 512; i < 1024; i++ {
		k := []interface{}{i}
		v, ok := t.Get(k)
		c.Assert(ok, IsFalse)
		c.Assert(v, IsNil)
	}
	// First
	k, v := t.First()
	c.Assert(k, NotNil)
	c.Assert(v, NotNil)
	c.Assert(k[0], Equals, 0)

	// Seek
	for i := 0; i < 512; i++ {
		k := []interface{}{i}
		e, ok := t.Seek(k)
		c.Assert(ok, IsTrue)
		c.Assert(e, NotNil)
		c.Assert(e.k[0], Equals, i)
		c.Assert(e.q.d[e.i].v[0], Equals, string(i))

		pk, pv, err := e.Prev()
		c.Assert(err, IsNil)
		c.Assert(pk, NotNil)
		c.Assert(pv, NotNil)
		c.Assert(pk[0], Equals, i)
		c.Assert(pv[0], Equals, string(i))
		pk, pv, err = e.Prev()
		if i > 0 {
			c.Assert(err, IsNil)
			c.Assert(pk, NotNil)
			c.Assert(pv, NotNil)
			c.Assert(pk[0], Equals, i-1)
			c.Assert(pv[0], Equals, string(i-1))
		}
	}

	t.Clear()
	e, err := t.SeekLast()
	c.Assert(e, IsNil)
	c.Assert(err, NotNil)
}
<|endoftext|>


<commit_before>package controllers

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

	"github.com/dgrijalva/jwt-go"

	"github.com/julienschmidt/httprouter"
	"gopkg.in/mgo.v2"

	"github.com/herald-it/goncord/models"
	. "github.com/herald-it/goncord/utils"
	"github.com/herald-it/goncord/utils/querying"
)

type ServiceController struct {
	session *mgo.Session
}

func (sc ServiceController) GetDB() *mgo.Database {
	return sc.session.DB(models.Set.Database.DbName)
}

func NewServiceController(s *mgo.Session) *ServiceController {
	return &ServiceController{s}
}

func (sc ServiceController) IsValid(
	w http.ResponseWriter,
	r *http.Request,
	ps httprouter.Params) *HttpError {

	collect := sc.GetDB().C(models.Set.Database.TokenTable)
	token := &models.DumpToken{}

	jwtCookie, cookieErr := r.Cookie("jwt")
	if cookieErr != nil {
		if err := r.ParseForm(); err != nil {
			return &HttpError{err, "Post form can not be parsed.", 500}
		}

		if err := Fill(token, r.PostForm); err != nil {
			return &HttpError{err, "Post form is not consistent with structure.", 500}
		}
	} else {
		token.Token = jwtCookie.Value
	}

	if token.Token == "" {
		return &HttpError{nil, "Invalid token value.", 500}
	}

	findDumpToken, err := querying.FindDumpToken(token, collect)
	if err != nil || findDumpToken == nil {
		return &HttpError{err, "Token not found.", 500}
	}

	tokenPars, err := jwt.Parse(findDumpToken.Token, nil)
	lifeTime := tokenPars.Claims["iat"]

	timeSpan := time.Now().Unix() - int64(lifeTime.(float64))
	if timeSpan > (7 * 24 * 60 * 60) {
		collect.Remove(findDumpToken)
		return &HttpError{nil, "Time token life has expired.", 500}
	}

	usr := new(models.User)
	usr.Id = findDumpToken.UserId

	findUsr, err := querying.FindUser(usr, sc.GetDB().C(models.Set.Database.UserTable))
	if err != nil {
		return &HttpError{err, "User not found.", 500}
	}

	jsonUsr, err := json.Marshal(findUsr)
	if err != nil {
		return &HttpError{err, "User can not convert to json.", 500}
	}

	w.Write(jsonUsr)
	return nil
}
<commit_msg>Refactoring<commit_after>package controllers

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

	"github.com/dgrijalva/jwt-go"

	"github.com/julienschmidt/httprouter"
	"gopkg.in/mgo.v2"

	"github.com/herald-it/goncord/models"
	. "github.com/herald-it/goncord/utils"
	"github.com/herald-it/goncord/utils/querying"
)

type ServiceController struct {
	session *mgo.Session
}

func (sc ServiceController) GetDB() *mgo.Database {
	return sc.session.DB(models.Set.Database.DbName)
}

func NewServiceController(s *mgo.Session) *ServiceController {
	return &ServiceController{s}
}

// IsValid Check the token for validity.
// The token can be a cookie or transferred
// post the form. First we checked the cookies.
// If the token is valid, the response will contain
// user model in json format.
func (sc ServiceController) IsValid(
	w http.ResponseWriter,
	r *http.Request,
	ps httprouter.Params) *HttpError {

	collect := sc.GetDB().C(models.Set.Database.TokenTable)
	token := &models.DumpToken{}

	tokenTmp, httpErr := getToken(r)
	if httpErr != nil {
		return httpErr
	}
	token.Token = tokenTmp

	if token.Token == "" {
		return &HttpError{nil, "Invalid token value.", 500}
	}

	findDumpToken, err := querying.FindDumpToken(token, collect)
	if err != nil || findDumpToken == nil {
		return &HttpError{err, "Token not found.", 500}
	}

	tokenParse, err := jwt.Parse(findDumpToken.Token, nil)
	if checkLifeTime(tokenParse) {
		collect.Remove(findDumpToken)
		return &HttpError{nil, "Time token life has expired.", 500}
	}

	usr := new(models.User)
	usr.Id = findDumpToken.UserId

	findUsr, err := querying.FindUser(usr, sc.GetDB().C(models.Set.Database.UserTable))
	if err != nil {
		return &HttpError{err, "User not found.", 500}
	}

	jsonUsr, err := json.Marshal(findUsr)
	if err != nil {
		return &HttpError{err, "User can not convert to json.", 500}
	}

	w.Write(jsonUsr)
	return nil
}

// getToken returns the token from the cookie,
// if the cookie is not present in the token, then looking in
// post the form if the token is not exist, then returned
// an empty string and error code.
func getToken(r *http.Request) (string, *HttpError) {
	jwtCookie, err := r.Cookie("jwt")
	if err != nil {
		if err := r.ParseForm(); err != nil {
			return "", &HttpError{err, "Post form can not be parsed.", 500}
		}

		token := r.PostForm.Get("jwt")
		return token, nil
	}

	return jwtCookie.Value, nil
}

// checkLifeTime checks the token lifetime.
func checkLifeTime(token *jwt.Token) bool {
	lifeTime := token.Claims["iat"]
	timeSpan := time.Now().Unix() - int64(lifeTime.(float64))

	return timeSpan > (7 * 24 * 60 * 60)
}
<|endoftext|>


<commit_before>package nsq

const LookupProtocolV1Magic = "  V1"

const (
	LookupClientStateV1Init         = 0
)

var (
	LookupClientErrV1Invalid    = ClientError{"E_INVALID"}
)

type LookupProtocolV1 interface {
	Protocol
	GET(client StatefulReadWriter, params []string) ([]byte, error)
	SET(client StatefulReadWriter, params []string) ([]byte, error)
}
<commit_msg>protocol input change to take int for size and then read binary data of specified size<commit_after>package nsq

const LookupProtocolV1Magic = "  V1"

const (
	LookupClientStateV1Init = 0
)

var (
	LookupClientErrV1Invalid = ClientError{"E_INVALID"}
)

type LookupProtocolV1 interface {
	Protocol
	GET(client StatefulReadWriter, params []string) ([]byte, error)
	SET(client StatefulReadWriter, params []string) ([]byte, error)
}
<|endoftext|>


<commit_before>package langsrvr

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"github.com/powerman/rpc-codec/jsonrpc2"
	"io"
	"net/rpc"
	"os"
	"os/exec"
	"strconv"
)

type Position struct {
	Line      int
	Character int
}

type textRange struct {
	Start Position
	End   Position
}

type ioReadWriteCloser struct {
	io.ReadCloser
	io.WriteCloser
}

//From https://github.com/natefinch/pie
func (rw ioReadWriteCloser) Close() error {
	err := rw.ReadCloser.Close()
	if err := rw.WriteCloser.Close(); err != nil {
		return err
	}
	return err
}

func (rw ioReadWriteCloser) Write(buf []byte) (int, error) {
	fmt.Printf("--> %s\n", string(buf))
	contentLength := len(buf)
	header := fmt.Sprintf("Content-Length: %d\r\n\r\n", contentLength)
	rw.WriteCloser.Write([]byte(header))
	n, err := rw.WriteCloser.Write(buf)
	return n, err
}

func (rw ioReadWriteCloser) Read(p []byte) (int, error) {
	headerReader := bufio.NewReader(rw.ReadCloser)
	headerReader.ReadLine()
	next, _ := headerReader.Peek(1)
	for !bytes.Equal(next, []byte("{")) {
		headerReader.ReadLine()
		next, _ = headerReader.Peek(1)
	}
	n, err := headerReader.Read(p)
	fmt.Printf("<-- %s\n", string(p))
	return n, err
}

type LangSrvr struct {
	conn     *jsonrpc2.Client
	handlers map[string]func([]string) string
}

func NewLangSrvr(command string) *LangSrvr {
	//Make a go-langserver to test
	server := exec.Command(command)
	fmt.Println("Starting langserver!")
	srvIn, err := server.StdinPipe()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	srvOut, err := server.StdoutPipe()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	serverConn := ioReadWriteCloser{srvOut, srvIn}
	err = server.Start()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	var lspRPC LangSrvr
	lspRPC.handlers = make(map[string](func([]string) string))
	lspRPC.conn = jsonrpc2.NewClient(serverConn)
	return &lspRPC
}

func (ls *LangSrvr) Initialize() {
	wd, _ := os.Getwd()
	fileUri := "file://" + wd
	params := map[string]interface{}{"processId": os.Getpid(), "rootUri": fileUri, "rootPath": wd,
		"capabilities": make(map[string]interface{})}
	//TODO: use capabilities to assign handlers
	ls.handlers["textDocument/hover"] = ls.tdHover
	ls.handlers["textDocument/signatureHelp"] = ls.tdSigHelp
	ls.execCommandSync("initialize", params, nil)
	ls.notify("initialized", nil)
}

func (ls *LangSrvr) Handle(cmd string, args []string) (string, error) {
	handler, ok := ls.handlers[cmd]
	if ok == false {
		return "", errors.New("Command does not exist")
	}
	return handler(args), nil
}

func (ls *LangSrvr) Shutdown() {
	ls.execCommandSync("shutdown", map[string]interface{}{}, nil)
}

//=======================================================

/*
* buffile, line, charachter
* textDocument/hover
* params:{textDocument:URI, position:{line:,character:}}
 */
func (ls *LangSrvr) tdHover(params []string) string {
	fmt.Printf("hover: %s\n", params)
	uri := "file://" + params[0]
	line, _ := strconv.Atoi(params[1])
	character, _ := strconv.Atoi(params[2])
	paramMap := map[string]interface{}{
		"textDocument": map[string]string{"uri": uri},
		"position": map[string]interface{}{
			"line":      line - 1,
			"character": character - 1,
		},
	}
	type MarkedString struct {
		Language string `json:"language,omitempty"`
		Value    string `json:"value,omitempty"`
		Simple   string `json:"simple,omitempty"`
	}
	reply := struct {
		Docs   []MarkedString `json:"contents"`
		Ranges textRange      `json:"range,omitempty"`
	}{}
	ls.execCommandSync("textDocument/hover", paramMap, &reply)
	fmt.Println(reply)
	return fmt.Sprintf("info -placement above -anchor %s.%s '%s'", params[1], params[2], reply.Docs[0])
}

/*
* textDocument/signatureHelp
* params:{textDocument:URI, position:{line:,character:}}
 */
func (ls *LangSrvr) tdSigHelp(params []string) string {
	fmt.Printf("sigHelp: %s\n", params)
	uri := "file://" + params[0]
	line, _ := strconv.Atoi(params[1])
	character, _ := strconv.Atoi(params[2])
	paramMap := map[string]interface{}{
		"textDocument": map[string]string{"uri": uri},
		"position": map[string]interface{}{
			"line":      line - 1,
			"character": character - 1,
		},
	}
	type sigInfo struct {
		Label  string                   `json:"label"`
		Docs   string                   `json:"documentation,omitempty"`
		Params []map[string]interface{} `json:"parameters,omitempty"`
	}
	reply := struct {
		Signatures []sigInfo `json:"signatures"`
		AParam     int       `json:"activeParameter,omitempty"`
		ASig       int       `json:"activeSignature,omitempty"`
	}{}
	err := ls.execCommandSync("textDocument/signatureHelp", paramMap, &reply)
	if err != nil {
		return "echo 'Command failed'"
	}
	fmt.Println(reply)
	return fmt.Sprintf("info -placement above -anchor %s.%s '%s\n%s'", params[1], params[2], reply.Signatures[reply.ASig].Label, reply.Signatures[reply.ASig].Docs)
}

func (ls *LangSrvr) execCommandSync(command string, params map[string]interface{}, reply interface{}) error {
	err := ls.conn.Call(command, params, reply)
	if err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF {
		fmt.Printf("Err1(): %q\n", err)
		return errors.New("RPC Error")
	} else if err != nil {
		rpcerr := jsonrpc2.ServerError(err)
		fmt.Printf("Err1(): code=%d msg=%q data=%v\n", rpcerr.Code, rpcerr.Message, rpcerr.Data)
		return errors.New("JSONRPC Error")
	}
	return nil
}

func (ls *LangSrvr) notify(method string, args interface{}) {
	ls.conn.Notify(method, args)
}
<commit_msg>Fixed MarkupString Unmarshaling, need to figure out a way to properly proxy reading, or use a different jsonrpc2 library<commit_after>package langsrvr

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/powerman/rpc-codec/jsonrpc2"
	"io"
	"net/rpc"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

type Position struct {
	Line      int
	Character int
}

type textRange struct {
	Start Position
	End   Position
}

type ioReadWriteCloser struct {
	io.ReadCloser
	io.WriteCloser
}

type MarkedString markedString

type markedString struct {
	Language string `json:"language,omitempty"`
	Value    string `json:"value,omitempty"`
	Simple   string `json:"simple,omitempty"`
}

func (m *MarkedString) UnmarshalJSON(data []byte) error {
	if d := strings.TrimSpace(string(data)); len(d) > 0 && d[0] == '"' {
		// Raw string
		var s string
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		m.Value = s
		return nil
	}
	// Language string
	ms := (*markedString)(m)
	return json.Unmarshal(data, ms)
}

//From https://github.com/natefinch/pie
func (rw ioReadWriteCloser) Close() error {
	err := rw.ReadCloser.Close()
	if err := rw.WriteCloser.Close(); err != nil {
		return err
	}
	return err
}

func (rw ioReadWriteCloser) Write(buf []byte) (int, error) {
	fmt.Printf("--> %s\n", string(buf))
	contentLength := len(buf)
	header := fmt.Sprintf("Content-Length: %d\r\n\r\n", contentLength)
	rw.WriteCloser.Write([]byte(header))
	n, err := rw.WriteCloser.Write(buf)
	return n, err
}

//Proxies reading in, so it may have to remove the header, which may or may not be present...
func (rw ioReadWriteCloser) Read(p []byte) (int, error) {
	headerReader := bufio.NewReader(rw.ReadCloser)
	headerReader.ReadLine()
	next, err := headerReader.Peek(1)
	for !bytes.Equal(next, []byte("{")) {
		headerReader.ReadLine()
		next, err = headerReader.Peek(1)
		if err != nil {
    		break
		}
	}
	n, err := headerReader.Read(p)
	fmt.Printf("<-- %s\n", string(p))
	return n, err
}

type LangSrvr struct {
	conn     *jsonrpc2.Client
	handlers map[string]func([]string) string
}

func NewLangSrvr(command string) *LangSrvr {
	//Make a go-langserver to test
	server := exec.Command(command)
	fmt.Println("Starting langserver!")
	srvIn, err := server.StdinPipe()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	srvOut, err := server.StdoutPipe()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	serverConn := ioReadWriteCloser{srvOut, srvIn}
	err = server.Start()
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	var lspRPC LangSrvr
	lspRPC.handlers = make(map[string](func([]string) string))
	lspRPC.conn = jsonrpc2.NewClient(serverConn)
	return &lspRPC
}

func (ls *LangSrvr) Initialize() {
	wd, _ := os.Getwd()
	fileUri := "file://" + wd
	params := map[string]interface{}{"processId": os.Getpid(), "rootUri": fileUri, "rootPath": wd,
		"capabilities": make(map[string]interface{})}
	//TODO: use capabilities to assign handlers
	ls.handlers["textDocument/hover"] = ls.tdHover
	ls.handlers["textDocument/signatureHelp"] = ls.tdSigHelp
	ls.execCommandSync("initialize", params, nil)
	ls.notify("initialized", nil)
}

func (ls *LangSrvr) Handle(cmd string, args []string) (string, error) {
	handler, ok := ls.handlers[cmd]
	if ok == false {
		return "", errors.New("Command does not exist")
	}
	return handler(args), nil
}

func (ls *LangSrvr) Shutdown() {
	ls.execCommandSync("shutdown", map[string]interface{}{}, nil)
}

//=======================================================

/*
* buffile, line, charachter
* textDocument/hover
* params:{textDocument:URI, position:{line:,character:}}
 */
func (ls *LangSrvr) tdHover(params []string) string {
	fmt.Printf("hover: %s\n", params)
	uri := "file://" + params[0]
	line, _ := strconv.Atoi(params[1])
	character, _ := strconv.Atoi(params[2])
	paramMap := map[string]interface{}{
		"textDocument": map[string]string{"uri": uri},
		"position": map[string]interface{}{
			"line":      line - 1,
			"character": character - 1,
		},
	}
	reply := struct {
		Docs   []MarkedString `json:"contents"`
		Ranges textRange      `json:"range,omitempty"`
	}{}
	err := ls.execCommandSync("textDocument/hover", paramMap, &reply)
	if err != nil {
		return "echo 'Command Failed'"
	}
	fmt.Println(reply)
	return fmt.Sprintf("info -placement below -anchor %s.%s '%s'", params[1], params[2], reply.Docs[0].Value)
}

/*
* textDocument/signatureHelp
* params:{textDocument:URI, position:{line:,character:}}
 */
func (ls *LangSrvr) tdSigHelp(params []string) string {
	fmt.Printf("sigHelp: %s\n", params)
	uri := "file://" + params[0]
	line, _ := strconv.Atoi(params[1])
	character, _ := strconv.Atoi(params[2])
	paramMap := map[string]interface{}{
		"textDocument": map[string]string{"uri": uri},
		"position": map[string]interface{}{
			"line":      line - 1,
			"character": character - 1,
		},
	}
	type sigInfo struct {
		Label  string                   `json:"label"`
		Docs   string                   `json:"documentation,omitempty"`
		Params []map[string]interface{} `json:"parameters,omitempty"`
	}
	reply := struct {
		Signatures []sigInfo `json:"signatures"`
		AParam     int       `json:"activeParameter,omitempty"`
		ASig       int       `json:"activeSignature,omitempty"`
	}{}
	err := ls.execCommandSync("textDocument/signatureHelp", paramMap, &reply)
	if err != nil {
		return "echo 'Command failed'"
	}
	fmt.Println(reply)
	return fmt.Sprintf("info -placement above -anchor %s.%s '%s\n%s'", params[1], params[2], reply.Signatures[reply.ASig].Label, reply.Signatures[reply.ASig].Docs)
}

func (ls *LangSrvr) execCommandSync(command string, params map[string]interface{}, reply interface{}) error {
	err := ls.conn.Call(command, params, reply)
	if err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF {
		fmt.Printf("Err1(): %q\n", err)
		return errors.New("RPC Error")
	} else if err != nil {
		rpcerr := jsonrpc2.ServerError(err)
		fmt.Printf("Err1(): code=%d msg=%q data=%v\n", rpcerr.Code, rpcerr.Message, rpcerr.Data)
		return errors.New("JSONRPC Error")
	}
	return nil
}

func (ls *LangSrvr) notify(method string, args interface{}) {
	ls.conn.Notify(method, args)
}
<|endoftext|>