<commit_before>// Package xmlsec is a wrapper around the xmlsec1 command
// https://www.aleksey.com/xmlsec/index.html
package xmlsec

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const (
	attrNameResponse     = `urn:oasis:names:tc:SAML:2.0:protocol:Response`
	attrNameAssertion    = `urn:oasis:names:tc:SAML:2.0:assertion:Assertion`
	attrNameAuthnRequest = `urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest`
)

type ValidationOptions struct {
	DTDFile          string
	EnableIDAttrHack bool
	IDAttrs          []string
}

// ErrSelfSignedCertificate is a typed error returned when xmlsec1 detects a
// self-signed certificate.
type ErrSelfSignedCertificate struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrSelfSignedCertificate) Error() string {
	return e.err.Error()
}

// ErrUnknownIssuer is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrUnknownIssuer struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrUnknownIssuer) Error() string {
	return e.err.Error()
}

// ErrValidityError is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrValidityError struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrValidityError) Error() string {
	return e.err.Error()
}

// Encrypt encrypts a byte sequence into an EncryptedData template using the
// given certificate and encryption method.
func Encrypt(template *EncryptedData, in []byte, publicCertPath string, method string) ([]byte, error) {
	// Writing template.
	fp, err := ioutil.TempFile("/tmp", "xmlsec")
	if err != nil {
		return nil, err
	}
	defer os.Remove(fp.Name())

	out, err := xml.MarshalIndent(template, "", "\t")
	if err != nil {
		return nil, err
	}
	_, err = fp.Write(out)
	if err != nil {
		return nil, err
	}
	if err := fp.Close(); err != nil {
		return nil, err
	}

	// Executing command.
	cmd := exec.Command("xmlsec1", "--encrypt",
		"--session-key", method,
		"--pubkey-cert-pem", publicCertPath,
		"--output", "/dev/stdout",
		"--xml-data", "/dev/stdin",
		fp.Name(),
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Decrypt takes an encrypted XML document and decrypts it using the given
// private key.
func Decrypt(in []byte, privateKeyPath string) ([]byte, error) {
	// Executing command.
	cmd := exec.Command("xmlsec1", "--decrypt",
		"--privkey-pem", privateKeyPath,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Verify takes a signed XML document and validates its signature.
func Verify(in []byte, publicCertPath string, opts *ValidationOptions) error {

	args := []string{
		"xmlsec1", "--verify",
		"--pubkey-cert-pem", publicCertPath,
		// Security: Don't ever use --enabled-reference-uris "local" value,
		// since it'd allow potential attackers to read local files using
		// <Reference URI="file:///etc/passwd"> hack!
		"--enabled-reference-uris", "empty,same-doc",
	}

	applyOptions(args, opts)

	args = append(args, []string{"/dev/stdin"}...)

	cmd := exec.Command(args[0], args[1:]...)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return err
	}

	if _, err := stdin.Write(in); err != nil {
		return err
	}

	if err := stdin.Close(); err != nil {
		return err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return err
	}

	if err := cmd.Wait(); err != nil || isValidityError(resErr) {
		if len(resErr) > 0 {
			return xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return err
	}

	return nil
}

// Sign takes a XML document and produces a signature.
func Sign(in []byte, privateKeyPath string, opts *ValidationOptions) (out []byte, err error) {

	args := []string{
		"xmlsec1", "--sign",
		"--privkey-pem", privateKeyPath,
		"--enabled-reference-uris", "empty,same-doc",
	}

	applyOptions(args, opts)

	args = append(args, []string{
		"--output", "/dev/stdout",
		"/dev/stdin",
	}...)

	cmd := exec.Command(args[0], args[1:]...)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil || isValidityError(resErr) {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return nil, err
	}

	return res, nil
}

func xmlsecErr(s string) error {
	err := fmt.Errorf("xmlsec: %s", strings.TrimSpace(s))
	if strings.HasPrefix(s, "OK") {
		return nil
	}
	if strings.Contains(err.Error(), "signature failed") {
		return err
	}
	if strings.Contains(err.Error(), "validity error") {
		return ErrValidityError{err}
	}
	if strings.Contains(err.Error(), "msg=self signed certificate") {
		return ErrSelfSignedCertificate{err}
	}
	if strings.Contains(err.Error(), "msg=unable to get local issuer certificate") {
		return ErrUnknownIssuer{err}
	}
	return err
}

func isValidityError(output []byte) bool {
	return bytes.Contains(output, []byte("validity error"))
}

func applyOptions(args []string, opts *ValidationOptions) {
	if opts == nil {
		return
	}

	if opts.DTDFile != "" {
		args = append(args, []string{
			"--dtd-file", opts.DTDFile,
		}...)
	}

	if opts.EnableIDAttrHack {
		args = append(args, []string{
			"--id-attr:ID", attrNameResponse,
			"--id-attr:ID", attrNameAssertion,
			"--id-attr:ID", attrNameAuthnRequest,
		}...)
		for _, v := range opts.IDAttrs {
			args = append(args, []string{"--id-attr:ID", v}...)
		}
	}
}
<commit_msg>pass reference to applyOptions<commit_after>// Package xmlsec is a wrapper around the xmlsec1 command
// https://www.aleksey.com/xmlsec/index.html
package xmlsec

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const (
	attrNameResponse     = `urn:oasis:names:tc:SAML:2.0:protocol:Response`
	attrNameAssertion    = `urn:oasis:names:tc:SAML:2.0:assertion:Assertion`
	attrNameAuthnRequest = `urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest`
)

type ValidationOptions struct {
	DTDFile          string
	EnableIDAttrHack bool
	IDAttrs          []string
}

// ErrSelfSignedCertificate is a typed error returned when xmlsec1 detects a
// self-signed certificate.
type ErrSelfSignedCertificate struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrSelfSignedCertificate) Error() string {
	return e.err.Error()
}

// ErrUnknownIssuer is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrUnknownIssuer struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrUnknownIssuer) Error() string {
	return e.err.Error()
}

// ErrValidityError is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrValidityError struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrValidityError) Error() string {
	return e.err.Error()
}

// Encrypt encrypts a byte sequence into an EncryptedData template using the
// given certificate and encryption method.
func Encrypt(template *EncryptedData, in []byte, publicCertPath string, method string) ([]byte, error) {
	// Writing template.
	fp, err := ioutil.TempFile("/tmp", "xmlsec")
	if err != nil {
		return nil, err
	}
	defer os.Remove(fp.Name())

	out, err := xml.MarshalIndent(template, "", "\t")
	if err != nil {
		return nil, err
	}
	_, err = fp.Write(out)
	if err != nil {
		return nil, err
	}
	if err := fp.Close(); err != nil {
		return nil, err
	}

	// Executing command.
	cmd := exec.Command("xmlsec1", "--encrypt",
		"--session-key", method,
		"--pubkey-cert-pem", publicCertPath,
		"--output", "/dev/stdout",
		"--xml-data", "/dev/stdin",
		fp.Name(),
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Decrypt takes an encrypted XML document and decrypts it using the given
// private key.
func Decrypt(in []byte, privateKeyPath string) ([]byte, error) {
	// Executing command.
	cmd := exec.Command("xmlsec1", "--decrypt",
		"--privkey-pem", privateKeyPath,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Verify takes a signed XML document and validates its signature.
func Verify(in []byte, publicCertPath string, opts *ValidationOptions) error {

	args := []string{
		"xmlsec1", "--verify",
		"--pubkey-cert-pem", publicCertPath,
		// Security: Don't ever use --enabled-reference-uris "local" value,
		// since it'd allow potential attackers to read local files using
		// <Reference URI="file:///etc/passwd"> hack!
		"--enabled-reference-uris", "empty,same-doc",
	}

	applyOptions(&args, opts)

	args = append(args, []string{"/dev/stdin"}...)

	cmd := exec.Command(args[0], args[1:]...)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return err
	}

	if _, err := stdin.Write(in); err != nil {
		return err
	}

	if err := stdin.Close(); err != nil {
		return err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return err
	}

	if err := cmd.Wait(); err != nil || isValidityError(resErr) {
		if len(resErr) > 0 {
			return xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return err
	}

	return nil
}

// Sign takes a XML document and produces a signature.
func Sign(in []byte, privateKeyPath string, opts *ValidationOptions) (out []byte, err error) {

	args := []string{
		"xmlsec1", "--sign",
		"--privkey-pem", privateKeyPath,
		"--enabled-reference-uris", "empty,same-doc",
	}

	applyOptions(&args, opts)

	args = append(args, []string{
		"--output", "/dev/stdout",
		"/dev/stdin",
	}...)

	cmd := exec.Command(args[0], args[1:]...)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil || isValidityError(resErr) {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return nil, err
	}

	return res, nil
}

func xmlsecErr(s string) error {
	err := fmt.Errorf("xmlsec: %s", strings.TrimSpace(s))
	if strings.HasPrefix(s, "OK") {
		return nil
	}
	if strings.Contains(err.Error(), "signature failed") {
		return err
	}
	if strings.Contains(err.Error(), "validity error") {
		return ErrValidityError{err}
	}
	if strings.Contains(err.Error(), "msg=self signed certificate") {
		return ErrSelfSignedCertificate{err}
	}
	if strings.Contains(err.Error(), "msg=unable to get local issuer certificate") {
		return ErrUnknownIssuer{err}
	}
	return err
}

func isValidityError(output []byte) bool {
	return bytes.Contains(output, []byte("validity error"))
}

func applyOptions(args *[]string, opts *ValidationOptions) {
	if opts == nil {
		return
	}

	if opts.DTDFile != "" {
		*args = append(*args, []string{
			"--dtd-file", opts.DTDFile,
		}...)
	}

	if opts.EnableIDAttrHack {
		*args = append(*args, []string{
			"--id-attr:ID", attrNameResponse,
			"--id-attr:ID", attrNameAssertion,
			"--id-attr:ID", attrNameAuthnRequest,
		}...)
		for _, v := range opts.IDAttrs {
			*args = append(*args, []string{"--id-attr:ID", v}...)
		}
	}
}
<|endoftext|>


<commit_before>package gettext

import (
	"sync"

	gotext "gopkg.in/leonelquinteros/gotext.v1"
)

var Languages = []string{"en", "ja"}
var defaultGettext *Gettext
var defaultLocale string
var defaultLocaleMutex sync.RWMutex
var defaultDomain string
var defaultDomainMutex sync.RWMutex
var initOnce sync.Once

func Default() *Gettext {
	initOnce.Do(func() {
		defaultGettext = New("locales")
		defaultGettext.AddDomain("messages")

		SetLocale(Languages[0])
		SetDomain("messages")
	})
	return defaultGettext
}

func New(path string) *Gettext {
	var v Gettext

	for _, l := range Languages {
		v.AddLocale(l, gotext.NewLocale(path, l))
	}

	return &v
}

func (v *Gettext) AddDomain(domain string) {
	v.mu.Lock()
	defer v.mu.Unlock()

	for _, l := range v.Locales {
		l.AddDomain(domain)
	}
}

func (v *Gettext) AddLocale(name string, l *gotext.Locale) {
	v.mu.Lock()
	defer v.mu.Unlock()

	if v.Locales == nil {
		v.Locales = map[string]*gotext.Locale{}
	}
	v.Locales[name] = l
}

func (v *Gettext) Get(locale, domain, name string, args ...interface{}) string {
	v.mu.RLock()
	defer v.mu.RUnlock()

	return v.Locales[locale].GetD(domain, name, args...)
}

func SetDomain(name string) {
	defaultDomainMutex.Lock()
	defer defaultDomainMutex.Unlock()

	defaultDomain = name
}

func SetLocale(name string) {
	defaultLocaleMutex.Lock()
	defer defaultLocaleMutex.Unlock()

	defaultLocale = name
}

func Get(s string, args ...interface{}) string {
	defaultDomainMutex.RLock()
	defaultLocaleMutex.RLock()
	defer defaultDomainMutex.RUnlock()
	defer defaultLocaleMutex.RUnlock()

	return Default().Get(defaultLocale, defaultDomain, s, args...)
}
<commit_msg>Fix use of locks<commit_after>package gettext

import (
	"sync"

	gotext "gopkg.in/leonelquinteros/gotext.v1"
)

var Languages = []string{"en", "ja"}
var defaultGettext *Gettext
var defaultLocale string
var defaultLocaleMutex sync.RWMutex
var defaultDomain string
var defaultDomainMutex sync.RWMutex
var initOnce sync.Once

func init() {
	SetLocale(Languages[0])
	SetDomain("messages")
}

func Default() *Gettext {
	initOnce.Do(func() {
		defaultGettext = New("locales")
		defaultGettext.AddDomain("messages")
	})
	return defaultGettext
}

func New(path string) *Gettext {
	var v Gettext

	for _, l := range Languages {
		v.AddLocale(l, gotext.NewLocale(path, l))
	}

	return &v
}

func (v *Gettext) AddDomain(domain string) {
	v.mu.Lock()
	defer v.mu.Unlock()

	for _, l := range v.Locales {
		l.AddDomain(domain)
	}
}

func (v *Gettext) AddLocale(name string, l *gotext.Locale) {
	v.mu.Lock()
	defer v.mu.Unlock()

	if v.Locales == nil {
		v.Locales = map[string]*gotext.Locale{}
	}
	v.Locales[name] = l
}

func (v *Gettext) Get(locale, domain, name string, args ...interface{}) string {
	v.mu.RLock()
	defer v.mu.RUnlock()

	return v.Locales[locale].GetD(domain, name, args...)
}

func SetDomain(name string) {
	defaultDomainMutex.Lock()
	defer defaultDomainMutex.Unlock()

	defaultDomain = name
}

func SetLocale(name string) {
	defaultLocaleMutex.Lock()
	defer defaultLocaleMutex.Unlock()

	defaultLocale = name
}

func Get(s string, args ...interface{}) string {
	l := Default()

	defaultDomainMutex.RLock()
	dd := defaultDomain
	defaultDomainMutex.RUnlock()

	defaultLocaleMutex.RLock()
	dl := defaultLocale
	defaultLocaleMutex.RUnlock()

	return l.Get(dl, dd, s, args...)
}
<|endoftext|>


<commit_before>package readers

import (
	"io"
	"io/ioutil"
	"testing"
)

// This is only a very basic test
func TestNonRepeatingSequenceReader(t *testing.T) {
	i := NewNonRepeatingSequence(0)
	a := []byte{0}
	b := []byte{0}

	i.Read(a)
	i.Read(b)

	if a[0] == b[0] {
		t.Fatal("Bytes should not be the same! %s vs %s", a, b)
	}
}

func TestNonRepeatingSequenceIsDifferent(t *testing.T) {
	i := NewNonRepeatingSequence(0)
	i2 := NewNonRepeatingSequence(5)

	a := []byte{0}
	b := []byte{0}

	commonalities := 0

	for x := 0; x < 100; x++ {
		i.Read(a)
		i2.Read(b)

		if a[0] == b[0] {
			commonalities += 1
		}
	}

	if commonalities > 5 {
		t.Fatal("Sequences are too similar")
	}
}

func BenchmarkNonRepeatingSequence(b *testing.B) {
	b.SetBytes(1)

	s := NewSizedNonRepeatingSequence(0, int64(b.N))

	b.StartTimer()
	_, err := io.Copy(ioutil.Discard, s)
	b.StopTimer()

	if err != nil {
		b.Fatal(err)
	}
}
<commit_msg>Use Fatalf<commit_after>package readers

import (
	"io"
	"io/ioutil"
	"testing"
)

// This is only a very basic test
func TestNonRepeatingSequenceReader(t *testing.T) {
	i := NewNonRepeatingSequence(0)
	a := []byte{0}
	b := []byte{0}

	i.Read(a)
	i.Read(b)

	if a[0] == b[0] {
		t.Fatalf("Bytes should not be the same! %s vs %s", a, b)
	}
}

func TestNonRepeatingSequenceIsDifferent(t *testing.T) {
	i := NewNonRepeatingSequence(0)
	i2 := NewNonRepeatingSequence(5)

	a := []byte{0}
	b := []byte{0}

	commonalities := 0

	for x := 0; x < 100; x++ {
		i.Read(a)
		i2.Read(b)

		if a[0] == b[0] {
			commonalities += 1
		}
	}

	if commonalities > 5 {
		t.Fatal("Sequences are too similar")
	}
}

func BenchmarkNonRepeatingSequence(b *testing.B) {
	b.SetBytes(1)

	s := NewSizedNonRepeatingSequence(0, int64(b.N))

	b.StartTimer()
	_, err := io.Copy(ioutil.Discard, s)
	b.StopTimer()

	if err != nil {
		b.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 The Libsacloud 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 internet

import (
	"context"
	"fmt"

	"github.com/sacloud/libsacloud/v2/sacloud"
	"github.com/sacloud/libsacloud/v2/sacloud/types"
)

// Builder スイッチ+ルータの構築を行う
type Builder struct {
	Name           string
	Description    string
	Tags           types.Tags
	IconID         types.ID
	NetworkMaskLen int
	BandWidthMbps  int
	EnableIPv6     bool

	Client *APIClient
}

// Validate 設定値の検証
func (b *Builder) Validate(ctx context.Context, zone string) error {
	requiredValues := map[string]bool{
		"NetworkMaskLen": b.NetworkMaskLen == 0,
		"BandWidthMbps":  b.BandWidthMbps == 0,
	}
	for key, empty := range requiredValues {
		if empty {
			return fmt.Errorf("%s is required", key)
		}
	}
	return nil
}

// Build ルータ+スイッチの作成や設定をまとめて行う
func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.Internet, error) {
	if err := b.Validate(ctx, zone); err != nil {
		return nil, err
	}

	internet, err := b.Client.Internet.Create(ctx, zone, &sacloud.InternetCreateRequest{
		Name:           b.Name,
		Description:    b.Description,
		Tags:           b.Tags,
		IconID:         b.IconID,
		NetworkMaskLen: b.NetworkMaskLen,
		BandWidthMbps:  b.BandWidthMbps,
	})
	if err != nil {
		return nil, err
	}

	// [HACK] ルータ作成直後は GET /internet/:id が404を返すことへの対応
	waiter := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
		return b.Client.Internet.Read(ctx, zone, internet.ID)
	}, 100)
	if _, err := waiter.WaitForState(ctx); err != nil {
		return nil, err
	}

	if b.EnableIPv6 {
		_, err = b.Client.Internet.EnableIPv6(ctx, zone, internet.ID)
		if err != nil {
			return nil, err
		}
	}

	return b.Client.Internet.Read(ctx, zone, internet.ID)
}

// Update スイッチ+ルータの更新
func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.Internet, error) {
	if err := b.Validate(ctx, zone); err != nil {
		return nil, err
	}

	// check Internet is exists
	internet, err := b.Client.Internet.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	if b.NetworkMaskLen != internet.NetworkMaskLen {
		return nil, fmt.Errorf("unsupported operation: NetworkMaskLen is changed: current: %d new: %d", internet.NetworkMaskLen, b.NetworkMaskLen)
	}

	internet, err = b.Client.Internet.Update(ctx, zone, internet.ID, &sacloud.InternetUpdateRequest{
		Name:        b.Name,
		Description: b.Description,
		Tags:        b.Tags,
		IconID:      b.IconID,
	})
	if err != nil {
		return nil, err
	}

	if internet.BandWidthMbps != b.BandWidthMbps {
		// 成功するとIDが変更となる
		internet, err = b.Client.Internet.UpdateBandWidth(ctx, zone, internet.ID, &sacloud.InternetUpdateBandWidthRequest{
			BandWidthMbps: b.BandWidthMbps,
		})
		if err != nil {
			return nil, err
		}
	}

	currentIPv6Enabled := len(internet.Switch.IPv6Nets) > 0
	if b.EnableIPv6 != currentIPv6Enabled {
		if currentIPv6Enabled {
			if err := b.Client.Internet.DisableIPv6(ctx, zone, internet.ID, internet.Switch.IPv6Nets[0].ID); err != nil {
				return nil, err
			}
		} else {
			if _, err := b.Client.Internet.EnableIPv6(ctx, zone, internet.ID); err != nil {
				return nil, err
			}
		}
	}

	return b.Client.Internet.Read(ctx, zone, internet.ID)
}
<commit_msg>Add NotFoundRetry field to Builder<commit_after>// Copyright 2016-2019 The Libsacloud 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 internet

import (
	"context"
	"fmt"

	"github.com/sacloud/libsacloud/v2/sacloud"
	"github.com/sacloud/libsacloud/v2/sacloud/types"
)

// DefaultNotFoundRetry スイッチ+ルータ作成後のReadで404が返ってこなくなるまでに許容する404エラーの回数
var DefaultNotFoundRetry = 360 // デフォルトの5秒おきリトライの場合30分

// Builder スイッチ+ルータの構築を行う
type Builder struct {
	Name           string
	Description    string
	Tags           types.Tags
	IconID         types.ID
	NetworkMaskLen int
	BandWidthMbps  int
	EnableIPv6     bool

	NotFoundRetry int

	Client *APIClient
}

// Validate 設定値の検証
func (b *Builder) Validate(ctx context.Context, zone string) error {
	requiredValues := map[string]bool{
		"NetworkMaskLen": b.NetworkMaskLen == 0,
		"BandWidthMbps":  b.BandWidthMbps == 0,
	}
	for key, empty := range requiredValues {
		if empty {
			return fmt.Errorf("%s is required", key)
		}
	}
	return nil
}

// Build ルータ+スイッチの作成や設定をまとめて行う
func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.Internet, error) {
	if b.NotFoundRetry == 0 {
		b.NotFoundRetry = DefaultNotFoundRetry
	}

	if err := b.Validate(ctx, zone); err != nil {
		return nil, err
	}

	internet, err := b.Client.Internet.Create(ctx, zone, &sacloud.InternetCreateRequest{
		Name:           b.Name,
		Description:    b.Description,
		Tags:           b.Tags,
		IconID:         b.IconID,
		NetworkMaskLen: b.NetworkMaskLen,
		BandWidthMbps:  b.BandWidthMbps,
	})
	if err != nil {
		return nil, err
	}

	// [HACK] ルータ作成直後は GET /internet/:id が404を返すことへの対応
	waiter := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
		return b.Client.Internet.Read(ctx, zone, internet.ID)
	}, b.NotFoundRetry)
	if _, err := waiter.WaitForState(ctx); err != nil {
		return nil, err
	}

	if b.EnableIPv6 {
		_, err = b.Client.Internet.EnableIPv6(ctx, zone, internet.ID)
		if err != nil {
			return nil, err
		}
	}

	return b.Client.Internet.Read(ctx, zone, internet.ID)
}

// Update スイッチ+ルータの更新
func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.Internet, error) {
	if err := b.Validate(ctx, zone); err != nil {
		return nil, err
	}

	// check Internet is exists
	internet, err := b.Client.Internet.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	if b.NetworkMaskLen != internet.NetworkMaskLen {
		return nil, fmt.Errorf("unsupported operation: NetworkMaskLen is changed: current: %d new: %d", internet.NetworkMaskLen, b.NetworkMaskLen)
	}

	internet, err = b.Client.Internet.Update(ctx, zone, internet.ID, &sacloud.InternetUpdateRequest{
		Name:        b.Name,
		Description: b.Description,
		Tags:        b.Tags,
		IconID:      b.IconID,
	})
	if err != nil {
		return nil, err
	}

	if internet.BandWidthMbps != b.BandWidthMbps {
		// 成功するとIDが変更となる
		internet, err = b.Client.Internet.UpdateBandWidth(ctx, zone, internet.ID, &sacloud.InternetUpdateBandWidthRequest{
			BandWidthMbps: b.BandWidthMbps,
		})
		if err != nil {
			return nil, err
		}
	}

	currentIPv6Enabled := len(internet.Switch.IPv6Nets) > 0
	if b.EnableIPv6 != currentIPv6Enabled {
		if currentIPv6Enabled {
			if err := b.Client.Internet.DisableIPv6(ctx, zone, internet.ID, internet.Switch.IPv6Nets[0].ID); err != nil {
				return nil, err
			}
		} else {
			if _, err := b.Client.Internet.EnableIPv6(ctx, zone, internet.ID); err != nil {
				return nil, err
			}
		}
	}

	return b.Client.Internet.Read(ctx, zone, internet.ID)
}
<|endoftext|>


<commit_before>package ccbridge_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"

	"github.com/cloudfoundry-incubator/inigo/fixtures"
	"github.com/cloudfoundry-incubator/inigo/helpers"
	"github.com/cloudfoundry-incubator/inigo/world"
	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"

	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	archive_helper "github.com/pivotal-golang/archiver/extractor/test_helper"
)

var _ = Describe("AppRunner", func() {
	var appId string

	var (
		runtime ifrit.Process
		bridge  ifrit.Process
	)

	BeforeEach(func() {
		appId = factories.GenerateGuid()

		fileServer, fileServerStaticDir := componentMaker.FileServer()

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"receptor", componentMaker.Receptor()},
			{"exec", componentMaker.Executor()},
			{"rep", componentMaker.Rep()},
			{"auctioneer", componentMaker.Auctioneer()},
			{"route-emitter", componentMaker.RouteEmitter()},
			{"converger", componentMaker.Converger()},
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
		}))

		bridge = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"tps", componentMaker.TPS()},
			{"nsync-listener", componentMaker.NsyncListener()},
		}))

		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "droplet.zip"),
			fixtures.HelloWorldIndexApp(),
		)

		helpers.Copy(
			componentMaker.Artifacts.Circuses[componentMaker.Stack],
			filepath.Join(fileServerStaticDir, world.CircusFilename),
		)
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime, bridge)
	})

	Describe("Running", func() {
		Context("when the running message contains a start_command", func() {
			It("runs the app on the executor, registers routes, and shows that they are running via the tps", func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
			        "process_guid": "process-guid",
			        "droplet_uri": "%s",
				      "stack": "%s",
			        "start_command": "bash server.sh",
			        "num_instances": 3,
			        "environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
			        "routes": ["route-1", "route-2"],
			        "log_guid": "%s"
			      }
			    `,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// check lrp instance statuses
				Eventually(helpers.RunningLRPInstancesPoller(componentMaker.Addresses.TPS, "process-guid")).Should(HaveLen(3))

				//both routes should be routable
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-1")).Should(Equal(http.StatusOK))
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-2")).Should(Equal(http.StatusOK))

				//a given route should route to all three running instances
				poller := helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "route-1")
				Eventually(poller).Should(Equal([]string{"0", "1", "2"}))
			})
		})

		Context("when the start message does not include a start_command", func() {
			It("runs the app, registers a route, and shows running via tps", func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
			        "process_guid": "process-guid",
			        "droplet_uri": "%s",
				      "stack": "%s",
			        "num_instances": 1,
			        "environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
			        "routes": ["route-1"],
			        "log_guid": "%s"
			      }
			    `,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(helpers.RunningLRPInstancesPoller(componentMaker.Addresses.TPS, "process-guid")).Should(HaveLen(1))
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-1")).Should(Equal(http.StatusOK))

				//a given route should route to the running instance
				poller := helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "route-1")
				Eventually(poller).Should(Equal([]string{"0"}))
			})
		})
	})

	Describe("Stop Index", func() {
		Context("when there is an instance running", func() {
			BeforeEach(func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
			        "process_guid": "process-guid",
			        "droplet_uri": "%s",
				      "stack": "%s",
			        "start_command": "bash server.sh",
			        "num_instances": 3,
			        "environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
			        "routes": ["route-1", "route-2"],
			        "log_guid": "%s"
			      }
			    `,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// wait for intances to come up
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 1, 2))
			})

			It("stops the app on the desired index, and then eventually starts it back up", func() {
				stopMessage := []byte(`{"process_guid": "process-guid", "index": 1}`)
				err := natsClient.Publish("diego.stop.index", stopMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// wait for stop to take effect
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 2))

				// wait for system to re-converge on desired state
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 1, 2))
			})
		})
	})
})

func runningIndexPoller(tpsAddr string, guid string) func() []int {
	return func() []int {
		indexes := []int{}
		for _, instance := range helpers.RunningLRPInstances(tpsAddr, guid) {
			indexes = append(indexes, int(instance.Index))
		}
		return indexes
	}
}
<commit_msg>Propagate resource limits on desire app messages<commit_after>package ccbridge_test

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"

	"github.com/cloudfoundry-incubator/inigo/fixtures"
	"github.com/cloudfoundry-incubator/inigo/helpers"
	"github.com/cloudfoundry-incubator/inigo/world"
	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"

	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	archive_helper "github.com/pivotal-golang/archiver/extractor/test_helper"
)

var _ = Describe("AppRunner", func() {
	var appId string

	var (
		runtime ifrit.Process
		bridge  ifrit.Process
	)

	BeforeEach(func() {
		appId = factories.GenerateGuid()

		fileServer, fileServerStaticDir := componentMaker.FileServer()

		runtime = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"receptor", componentMaker.Receptor()},
			{"exec", componentMaker.Executor()},
			{"rep", componentMaker.Rep()},
			{"auctioneer", componentMaker.Auctioneer()},
			{"route-emitter", componentMaker.RouteEmitter()},
			{"converger", componentMaker.Converger()},
			{"router", componentMaker.Router()},
			{"file-server", fileServer},
		}))

		bridge = ginkgomon.Invoke(grouper.NewParallel(os.Kill, grouper.Members{
			{"tps", componentMaker.TPS()},
			{"nsync-listener", componentMaker.NsyncListener()},
		}))

		archive_helper.CreateZipArchive(
			filepath.Join(fileServerStaticDir, "droplet.zip"),
			fixtures.HelloWorldIndexApp(),
		)

		helpers.Copy(
			componentMaker.Artifacts.Circuses[componentMaker.Stack],
			filepath.Join(fileServerStaticDir, world.CircusFilename),
		)
	})

	AfterEach(func() {
		helpers.StopProcesses(runtime, bridge)
	})

	Describe("Running", func() {
		Context("when the running message contains a start_command", func() {
			It("runs the app on the executor, registers routes, and shows that they are running via the tps", func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
							"process_guid": "process-guid",
							"droplet_uri": "%s",
							"stack": "%s",
							"start_command": "bash server.sh",
							"num_instances": 3,
							"memory_mb": 256,
							"disk_mb": 1024,
							"file_descriptors": 16384,
							"environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
							"routes": ["route-1", "route-2"],
							"log_guid": "%s"
						}
						`,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// check lrp instance statuses
				Eventually(helpers.RunningLRPInstancesPoller(componentMaker.Addresses.TPS, "process-guid")).Should(HaveLen(3))

				//both routes should be routable
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-1")).Should(Equal(http.StatusOK))
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-2")).Should(Equal(http.StatusOK))

				//a given route should route to all three running instances
				poller := helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "route-1")
				Eventually(poller).Should(Equal([]string{"0", "1", "2"}))
			})
		})

		Context("when the start message does not include a start_command", func() {
			It("runs the app, registers a route, and shows running via tps", func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
							"process_guid": "process-guid",
							"droplet_uri": "%s",
							"stack": "%s",
							"num_instances": 1,
							"memory_mb": 256,
							"disk_mb": 1024,
							"file_descriptors": 16384,
							"environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
							"routes": ["route-1"],
							"log_guid": "%s"
						}
						`,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(helpers.RunningLRPInstancesPoller(componentMaker.Addresses.TPS, "process-guid")).Should(HaveLen(1))
				Eventually(helpers.ResponseCodeFromHostPoller(componentMaker.Addresses.Router, "route-1")).Should(Equal(http.StatusOK))

				//a given route should route to the running instance
				poller := helpers.HelloWorldInstancePoller(componentMaker.Addresses.Router, "route-1")
				Eventually(poller).Should(Equal([]string{"0"}))
			})
		})
	})

	Describe("Stop Index", func() {
		Context("when there is an instance running", func() {
			BeforeEach(func() {
				runningMessage := []byte(
					fmt.Sprintf(
						`
						{
							"process_guid": "process-guid",
							"droplet_uri": "%s",
							"stack": "%s",
							"start_command": "bash server.sh",
							"num_instances": 3,
							"environment":[{"name":"VCAP_APPLICATION", "value":"{}"}],
							"memory_mb": 256,
							"disk_mb": 1024,
							"file_descriptors": 16384,
							"routes": ["route-1", "route-2"],
							"log_guid": "%s"
						}
						`,
						fmt.Sprintf("http://%s/v1/static/%s", componentMaker.Addresses.FileServer, "droplet.zip"),
						componentMaker.Stack,
						appId,
					),
				)

				// publish the app run message
				err := natsClient.Publish("diego.desire.app", runningMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// wait for intances to come up
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 1, 2))
			})

			It("stops the app on the desired index, and then eventually starts it back up", func() {
				stopMessage := []byte(`{"process_guid": "process-guid", "index": 1}`)
				err := natsClient.Publish("diego.stop.index", stopMessage)
				Ω(err).ShouldNot(HaveOccurred())

				// wait for stop to take effect
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 2))

				// wait for system to re-converge on desired state
				Eventually(runningIndexPoller(componentMaker.Addresses.TPS, "process-guid")).Should(ConsistOf(0, 1, 2))
			})
		})
	})
})

func runningIndexPoller(tpsAddr string, guid string) func() []int {
	return func() []int {
		indexes := []int{}
		for _, instance := range helpers.RunningLRPInstances(tpsAddr, guid) {
			indexes = append(indexes, int(instance.Index))
		}
		return indexes
	}
}
<|endoftext|>


<commit_before>package ccd_test

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/kdar/health/ccd"
	"github.com/kylelemons/godebug/pretty"
)

func TestParse_Medications(t *testing.T) {
	c := ccd.NewDefaultCCD()
	err := parseAndRecover(t, c, "testdata/specific/medications.xml", nil)
	if err != nil {
		t.Fatal(err)
	}

	meds := []ccd.Medication{
		ccd.Medication{
			Name:           "Albuterol 0.09 MG/ACTUAT inhalant solution",
			Administration: "",
			Dose: ccd.MedicationDose{
				LowValue:  "0.09",
				LowUnit:   "mg/actuat",
				HighValue: "",
				HighUnit:  "",
			},
			Status:     "Active",
			StatusCode: "completed",
			StartDate:  time.Time{},
			StopDate:   time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC),
			Period:     time.Duration(43200000000000),
			Code: ccd.Code{
				CodeSystemName: "",
				Type:           "",
				CodeSystem:     "2.16.840.1.113883.6.88",
				Code:           "573621",
				DisplayName:    "Albuterol 0.09 MG/ACTUAT inhalant solution",
				Translations: []ccd.Code{ccd.Code{
					CodeSystemName: "RxNorm",
					Type:           "",
					CodeSystem:     "2.16.840.1.113883.6.88",
					Code:           "573621",
					DisplayName:    "Proventil 0.09 MG/ACTUAT inhalant solution",
					OriginalText:   "",
				}},
			},
			Reason: &ccd.MedicationReason{
				Value: ccd.Code{
					CodeSystemName: "",
					Type:           "CD",
					CodeSystem:     "2.16.840.1.113883.6.96",
					Code:           "233604007",
					DisplayName:    "Pneumonia",
				},
				Date: time.Time{},
			},
		},
	}

	if len(meds) != len(c.Medications) {
		t.Fatalf("Expected %d medications. Got %d", len(meds), len(c.Medications))
	}

	for i, _ := range meds {
		if !reflect.DeepEqual(meds[i], c.Medications[i]) {
			fmt.Println(pretty.Compare(meds[i], c.Medications[i]))
			t.Fatal()
		}

	}

}
<commit_msg>Fixed output of medications test<commit_after>package ccd_test

import (
	"reflect"
	"testing"
	"time"

	"github.com/kdar/health/ccd"
	"github.com/kylelemons/godebug/pretty"
)

func TestParse_Medications(t *testing.T) {
	c := ccd.NewDefaultCCD()
	err := parseAndRecover(t, c, "testdata/specific/medications.xml", nil)
	if err != nil {
		t.Fatal(err)
	}

	meds := []ccd.Medication{
		ccd.Medication{
			Name:           "Albuterol 0.09 MG/ACTUAT inhalant solution",
			Administration: "",
			Dose: ccd.MedicationDose{
				LowValue:  "0.09",
				LowUnit:   "mg/actuat",
				HighValue: "",
				HighUnit:  "",
			},
			Status:     "Active",
			StatusCode: "completed",
			StartDate:  time.Time{},
			StopDate:   time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC),
			Period:     time.Duration(43200000000000),
			Code: ccd.Code{
				CodeSystemName: "",
				Type:           "",
				CodeSystem:     "2.16.840.1.113883.6.88",
				Code:           "573621",
				DisplayName:    "Albuterol 0.09 MG/ACTUAT inhalant solution",
				Translations: []ccd.Code{ccd.Code{
					CodeSystemName: "RxNorm",
					Type:           "",
					CodeSystem:     "2.16.840.1.113883.6.88",
					Code:           "573621",
					DisplayName:    "Proventil 0.09 MG/ACTUAT inhalant solution",
					OriginalText:   "",
				}},
			},
			Reason: &ccd.MedicationReason{
				Value: ccd.Code{
					CodeSystemName: "",
					Type:           "CD",
					CodeSystem:     "2.16.840.1.113883.6.96",
					Code:           "233604007",
					DisplayName:    "Pneumonia",
				},
				Date: time.Time{},
			},
		},
	}

	if len(meds) != len(c.Medications) {
		t.Fatalf("Expected %d medications. Got %d", len(meds), len(c.Medications))
	}

	for i, _ := range meds {
		if !reflect.DeepEqual(meds[i], c.Medications[i]) {
			t.Fatalf("Differences in medication %d: %v", i, pretty.Compare(meds[i], c.Medications[i]))
		}
	}
}
<|endoftext|>


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

package clevergo

import (
	"fmt"
	"github.com/valyala/fasthttp"
	"os"
	"time"
)

const (
	Version = "1.0.3"
	Logo    = `  ____ _     _______     _______ ____   ____  ___
 / ___| |   | ____\ \   / / ____|  _ \ / ___|/ _ \
| |   | |   |  _|  \ \ / /|  _| | |_) | |  _| | | |
| |___| |___| |___  \ V / | |___|  _ <| |_| | |_| |
 \____|_____|_____|  \_/  |_____|_| \_\\____|\___/ `
)

func info() {
	fmt.Printf("\x1b[36;1m%s %s\x1b[0m\n\n\x1b[32;1mStarted at %s\x1b[0m\n", Logo, Version, time.Now())
}

func ListenAndServe(addr string, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServe(addr, handler)
}

func ListenAndServeUNIX(addr string, mode os.FileMode, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeUNIX(addr, mode, handler)
}

func ListenAndServeTLS(addr, certFile, keyFile string, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeTLS(addr, certFile, keyFile, handler)
}

func ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeTLSEmbed(addr, certData, keyData, handler)
}
<commit_msg>Upgraded version to v1.1.0<commit_after>// Copyright 2016 HeadwindFly. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clevergo

import (
	"fmt"
	"github.com/valyala/fasthttp"
	"os"
	"time"
)

const (
	Version = "1.1.0"
	Logo    = `  ____ _     _______     _______ ____   ____  ___
 / ___| |   | ____\ \   / / ____|  _ \ / ___|/ _ \
| |   | |   |  _|  \ \ / /|  _| | |_) | |  _| | | |
| |___| |___| |___  \ V / | |___|  _ <| |_| | |_| |
 \____|_____|_____|  \_/  |_____|_| \_\\____|\___/ `
)

func info() {
	fmt.Printf("\x1b[36;1m%s %s\x1b[0m\n\n\x1b[32;1mStarted at %s\x1b[0m\n", Logo, Version, time.Now())
}

func ListenAndServe(addr string, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServe(addr, handler)
}

func ListenAndServeUNIX(addr string, mode os.FileMode, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeUNIX(addr, mode, handler)
}

func ListenAndServeTLS(addr, certFile, keyFile string, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeTLS(addr, certFile, keyFile, handler)
}

func ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler fasthttp.RequestHandler) error {
	info()
	return fasthttp.ListenAndServeTLSEmbed(addr, certData, keyData, handler)
}
<|endoftext|>


<commit_before>package cli

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

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
)

// Parse command line options that are passed in for Terragrunt
func ParseTerragruntOptions(cliContext *cli.Context) (*options.TerragruntOptions, error) {
	terragruntOptions, err := parseTerragruntOptionsFromArgs(cliContext.Args(), cliContext.App.Writer, cliContext.App.ErrWriter)
	if err != nil {
		return nil, err
	}
	return terragruntOptions, nil
}

// TODO: replace the urfave CLI library with something else.
//
// EXPLANATION: The normal way to parse flags with the urfave CLI library would be to define the flags in the
// CreateTerragruntCLI method and to read the values of those flags using cliContext.String(...),
// cliContext.Bool(...), etc. Unfortunately, this does not work here due to a limitation in the urfave
// CLI library: if the user passes in any "command" whatsoever, (e.g. the "apply" in "terragrunt apply"), then
// any flags that come after it are not parsed (e.g. the "--foo" is not parsed in "terragrunt apply --foo").
// Therefore, we have to parse options ourselves, which is infuriating. For more details on this limitation,
// see: https://github.com/urfave/cli/issues/533. For now, our workaround is to dumbly loop over the arguments
// and look for the ones we need, but in the future, we should change to a different CLI library to avoid this
// limitation.
func parseTerragruntOptionsFromArgs(args []string, writer, errWriter io.Writer) (*options.TerragruntOptions, error) {
	currentDir, err := os.Getwd()
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	workingDir, err := parseStringArg(args, OPT_WORKING_DIR, currentDir)
	if err != nil {
		return nil, err
	}

	downloadDirRaw, err := parseStringArg(args, OPT_DOWNLOAD_DIR, os.Getenv("TERRAGRUNT_DOWNLOAD"))
	if err != nil {
		return nil, err
	}
	if downloadDirRaw == "" {
		downloadDirRaw = util.JoinPath(workingDir, options.TerragruntCacheDir)
	}
	downloadDir, err := filepath.Abs(downloadDirRaw)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	terragruntConfigPath, err := parseStringArg(args, OPT_TERRAGRUNT_CONFIG, os.Getenv("TERRAGRUNT_CONFIG"))
	if err != nil {
		return nil, err
	}
	if terragruntConfigPath == "" {
		terragruntConfigPath = config.DefaultConfigPath(workingDir)
	}

	terraformPath, err := parseStringArg(args, OPT_TERRAGRUNT_TFPATH, os.Getenv("TERRAGRUNT_TFPATH"))
	if err != nil {
		return nil, err
	}
	if terraformPath == "" {
		terraformPath = "terraform"
	}

	terraformSource, err := parseStringArg(args, OPT_TERRAGRUNT_SOURCE, os.Getenv("TERRAGRUNT_SOURCE"))
	if err != nil {
		return nil, err
	}

	sourceUpdate := parseBooleanArg(args, OPT_TERRAGRUNT_SOURCE_UPDATE, os.Getenv("TERRAGRUNT_SOURCE_UPDATE") == "true" || os.Getenv("TERRAGRUNT_SOURCE_UPDATE") == "1")

	ignoreDependencyErrors := parseBooleanArg(args, OPT_TERRAGRUNT_IGNORE_DEPENDENCY_ERRORS, false)

	iamRole, err := parseStringArg(args, OPT_TERRAGRUNT_IAM_ROLE, os.Getenv("TERRAGRUNT_IAM_ROLE"))
	if err != nil {
		return nil, err
	}

	excludeDirs, err := parseMultiStringArg(args, OPT_TERRAGRUNT_EXCLUDE_DIR, []string{})
	if err != nil {
		return nil, err
	}

	opts, err := options.NewTerragruntOptions(filepath.ToSlash(terragruntConfigPath))
	if err != nil {
		return nil, err
	}

	opts.TerraformPath = filepath.ToSlash(terraformPath)
	opts.AutoInit = !parseBooleanArg(args, OPT_TERRAGRUNT_NO_AUTO_INIT, os.Getenv("TERRAGRUNT_AUTO_INIT") == "false")
	opts.NonInteractive = parseBooleanArg(args, OPT_NON_INTERACTIVE, os.Getenv("TF_INPUT") == "false" || os.Getenv("TF_INPUT") == "0")
	opts.TerraformCliArgs = filterTerragruntArgs(args)
	opts.TerraformCommand = util.FirstArg(opts.TerraformCliArgs)
	opts.WorkingDir = filepath.ToSlash(workingDir)
	opts.DownloadDir = filepath.ToSlash(downloadDir)
	opts.Logger = util.CreateLoggerWithWriter(errWriter, "")
	opts.RunTerragrunt = runTerragrunt
	opts.Source = terraformSource
	opts.SourceUpdate = sourceUpdate
	opts.IgnoreDependencyErrors = ignoreDependencyErrors
	opts.Writer = writer
	opts.ErrWriter = errWriter
	opts.Env = parseEnvironmentVariables(os.Environ())
	opts.IamRole = iamRole
	opts.ExcludeDirs = excludeDirs

	return opts, nil
}

func filterTerraformExtraArgs(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) []string {
	out := []string{}
	cmd := util.FirstArg(terragruntOptions.TerraformCliArgs)

	for _, arg := range terragruntConfig.Terraform.ExtraArgs {
		for _, arg_cmd := range arg.Commands {
			if cmd == arg_cmd {
				out = append(out, arg.Arguments...)

				// If RequiredVarFiles is specified, add -var-file=<file> for each specified files
				for _, file := range util.RemoveDuplicatesFromListKeepLast(arg.RequiredVarFiles) {
					out = append(out, fmt.Sprintf("-var-file=%s", file))
				}

				// If OptionalVarFiles is specified, check for each file if it exists and if so, add -var-file=<file>
				// It is possible that many files resolve to the same path, so we remove duplicates.
				for _, file := range util.RemoveDuplicatesFromListKeepLast(arg.OptionalVarFiles) {
					if util.FileExists(file) {
						out = append(out, fmt.Sprintf("-var-file=%s", file))
					} else {
						terragruntOptions.Logger.Printf("Skipping var-file %s as it does not exist", file)
					}
				}
			}
		}
	}

	return out
}

func filterTerraformEnvVarsFromExtraArgs(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) map[string]string {
	out := map[string]string{}
	cmd := util.FirstArg(terragruntOptions.TerraformCliArgs)

	for _, arg := range terragruntConfig.Terraform.ExtraArgs {
		for _, argcmd := range arg.Commands {
			if cmd == argcmd {
				for k, v := range arg.EnvVars {
					out[k] = v
				}
			}
		}
	}

	return out
}

func parseEnvironmentVariables(environment []string) map[string]string {
	environmentMap := make(map[string]string)

	for i := 0; i < len(environment); i++ {
		variableSplit := strings.SplitN(environment[i], "=", 2)

		if len(variableSplit) == 2 {
			environmentMap[strings.TrimSpace(variableSplit[0])] = variableSplit[1]
		}
	}

	return environmentMap
}

// Return a copy of the given args with all Terragrunt-specific args removed
func filterTerragruntArgs(args []string) []string {
	out := []string{}
	for i := 0; i < len(args); i++ {
		arg := args[i]
		argWithoutPrefix := strings.TrimPrefix(arg, "--")

		if util.ListContainsElement(MULTI_MODULE_COMMANDS, arg) {
			// Skip multi-module commands entirely
			continue
		}

		if util.ListContainsElement(ALL_TERRAGRUNT_STRING_OPTS, argWithoutPrefix) {
			// String flags have the argument and the value, so skip both
			i = i + 1
			continue
		}
		if util.ListContainsElement(ALL_TERRAGRUNT_BOOLEAN_OPTS, argWithoutPrefix) {
			// Just skip the boolean flag
			continue
		}

		out = append(out, arg)
	}
	return out
}

// Find a boolean argument (e.g. --foo) of the given name in the given list of arguments. If it's present, return true.
// If it isn't, return defaultValue.
func parseBooleanArg(args []string, argName string, defaultValue bool) bool {
	for _, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			return true
		}
	}
	return defaultValue
}

// Find a string argument (e.g. --foo "VALUE") of the given name in the given list of arguments. If it's present,
// return its value. If it is present, but has no value, return an error. If it isn't present, return defaultValue.
func parseStringArg(args []string, argName string, defaultValue string) (string, error) {
	for i, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			if (i + 1) < len(args) {
				return args[i+1], nil
			} else {
				return "", errors.WithStackTrace(ArgMissingValue(argName))
			}
		}
	}
	return defaultValue, nil
}

// Find multiple string arguments of the same type (e.g. --foo "VALUE_A" --foo "VALUE_B") of the given name in the given list of arguments. If there are any present,
// return a list of all values. If there are any present, but one of them has no value, return an error. If there aren't any present, return defaultValue.
func parseMultiStringArg(args []string, argName string, defaultValue []string) ([]string, error) {
	stringArgs := []string{}
	fmt.Println(args)
	for i, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			if (i + 1) < len(args) {
				stringArgs = append(stringArgs, args[i+1])
			} else {
				return nil, errors.WithStackTrace(ArgMissingValue(argName))
			}
		}
	}
	if len(stringArgs) == 0 {
		return defaultValue, nil
	}

	fmt.Printf("STRING ARGS: %s \n", stringArgs)

	return stringArgs, nil
}

// Custom error types

type ArgMissingValue string

func (err ArgMissingValue) Error() string {
	return fmt.Sprintf("You must specify a value for the --%s option", string(err))
}
<commit_msg>Remove debug output<commit_after>package cli

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

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
)

// Parse command line options that are passed in for Terragrunt
func ParseTerragruntOptions(cliContext *cli.Context) (*options.TerragruntOptions, error) {
	terragruntOptions, err := parseTerragruntOptionsFromArgs(cliContext.Args(), cliContext.App.Writer, cliContext.App.ErrWriter)
	if err != nil {
		return nil, err
	}
	return terragruntOptions, nil
}

// TODO: replace the urfave CLI library with something else.
//
// EXPLANATION: The normal way to parse flags with the urfave CLI library would be to define the flags in the
// CreateTerragruntCLI method and to read the values of those flags using cliContext.String(...),
// cliContext.Bool(...), etc. Unfortunately, this does not work here due to a limitation in the urfave
// CLI library: if the user passes in any "command" whatsoever, (e.g. the "apply" in "terragrunt apply"), then
// any flags that come after it are not parsed (e.g. the "--foo" is not parsed in "terragrunt apply --foo").
// Therefore, we have to parse options ourselves, which is infuriating. For more details on this limitation,
// see: https://github.com/urfave/cli/issues/533. For now, our workaround is to dumbly loop over the arguments
// and look for the ones we need, but in the future, we should change to a different CLI library to avoid this
// limitation.
func parseTerragruntOptionsFromArgs(args []string, writer, errWriter io.Writer) (*options.TerragruntOptions, error) {
	currentDir, err := os.Getwd()
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	workingDir, err := parseStringArg(args, OPT_WORKING_DIR, currentDir)
	if err != nil {
		return nil, err
	}

	downloadDirRaw, err := parseStringArg(args, OPT_DOWNLOAD_DIR, os.Getenv("TERRAGRUNT_DOWNLOAD"))
	if err != nil {
		return nil, err
	}
	if downloadDirRaw == "" {
		downloadDirRaw = util.JoinPath(workingDir, options.TerragruntCacheDir)
	}
	downloadDir, err := filepath.Abs(downloadDirRaw)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	terragruntConfigPath, err := parseStringArg(args, OPT_TERRAGRUNT_CONFIG, os.Getenv("TERRAGRUNT_CONFIG"))
	if err != nil {
		return nil, err
	}
	if terragruntConfigPath == "" {
		terragruntConfigPath = config.DefaultConfigPath(workingDir)
	}

	terraformPath, err := parseStringArg(args, OPT_TERRAGRUNT_TFPATH, os.Getenv("TERRAGRUNT_TFPATH"))
	if err != nil {
		return nil, err
	}
	if terraformPath == "" {
		terraformPath = "terraform"
	}

	terraformSource, err := parseStringArg(args, OPT_TERRAGRUNT_SOURCE, os.Getenv("TERRAGRUNT_SOURCE"))
	if err != nil {
		return nil, err
	}

	sourceUpdate := parseBooleanArg(args, OPT_TERRAGRUNT_SOURCE_UPDATE, os.Getenv("TERRAGRUNT_SOURCE_UPDATE") == "true" || os.Getenv("TERRAGRUNT_SOURCE_UPDATE") == "1")

	ignoreDependencyErrors := parseBooleanArg(args, OPT_TERRAGRUNT_IGNORE_DEPENDENCY_ERRORS, false)

	iamRole, err := parseStringArg(args, OPT_TERRAGRUNT_IAM_ROLE, os.Getenv("TERRAGRUNT_IAM_ROLE"))
	if err != nil {
		return nil, err
	}

	excludeDirs, err := parseMultiStringArg(args, OPT_TERRAGRUNT_EXCLUDE_DIR, []string{})
	if err != nil {
		return nil, err
	}

	opts, err := options.NewTerragruntOptions(filepath.ToSlash(terragruntConfigPath))
	if err != nil {
		return nil, err
	}

	opts.TerraformPath = filepath.ToSlash(terraformPath)
	opts.AutoInit = !parseBooleanArg(args, OPT_TERRAGRUNT_NO_AUTO_INIT, os.Getenv("TERRAGRUNT_AUTO_INIT") == "false")
	opts.NonInteractive = parseBooleanArg(args, OPT_NON_INTERACTIVE, os.Getenv("TF_INPUT") == "false" || os.Getenv("TF_INPUT") == "0")
	opts.TerraformCliArgs = filterTerragruntArgs(args)
	opts.TerraformCommand = util.FirstArg(opts.TerraformCliArgs)
	opts.WorkingDir = filepath.ToSlash(workingDir)
	opts.DownloadDir = filepath.ToSlash(downloadDir)
	opts.Logger = util.CreateLoggerWithWriter(errWriter, "")
	opts.RunTerragrunt = runTerragrunt
	opts.Source = terraformSource
	opts.SourceUpdate = sourceUpdate
	opts.IgnoreDependencyErrors = ignoreDependencyErrors
	opts.Writer = writer
	opts.ErrWriter = errWriter
	opts.Env = parseEnvironmentVariables(os.Environ())
	opts.IamRole = iamRole
	opts.ExcludeDirs = excludeDirs

	return opts, nil
}

func filterTerraformExtraArgs(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) []string {
	out := []string{}
	cmd := util.FirstArg(terragruntOptions.TerraformCliArgs)

	for _, arg := range terragruntConfig.Terraform.ExtraArgs {
		for _, arg_cmd := range arg.Commands {
			if cmd == arg_cmd {
				out = append(out, arg.Arguments...)

				// If RequiredVarFiles is specified, add -var-file=<file> for each specified files
				for _, file := range util.RemoveDuplicatesFromListKeepLast(arg.RequiredVarFiles) {
					out = append(out, fmt.Sprintf("-var-file=%s", file))
				}

				// If OptionalVarFiles is specified, check for each file if it exists and if so, add -var-file=<file>
				// It is possible that many files resolve to the same path, so we remove duplicates.
				for _, file := range util.RemoveDuplicatesFromListKeepLast(arg.OptionalVarFiles) {
					if util.FileExists(file) {
						out = append(out, fmt.Sprintf("-var-file=%s", file))
					} else {
						terragruntOptions.Logger.Printf("Skipping var-file %s as it does not exist", file)
					}
				}
			}
		}
	}

	return out
}

func filterTerraformEnvVarsFromExtraArgs(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) map[string]string {
	out := map[string]string{}
	cmd := util.FirstArg(terragruntOptions.TerraformCliArgs)

	for _, arg := range terragruntConfig.Terraform.ExtraArgs {
		for _, argcmd := range arg.Commands {
			if cmd == argcmd {
				for k, v := range arg.EnvVars {
					out[k] = v
				}
			}
		}
	}

	return out
}

func parseEnvironmentVariables(environment []string) map[string]string {
	environmentMap := make(map[string]string)

	for i := 0; i < len(environment); i++ {
		variableSplit := strings.SplitN(environment[i], "=", 2)

		if len(variableSplit) == 2 {
			environmentMap[strings.TrimSpace(variableSplit[0])] = variableSplit[1]
		}
	}

	return environmentMap
}

// Return a copy of the given args with all Terragrunt-specific args removed
func filterTerragruntArgs(args []string) []string {
	out := []string{}
	for i := 0; i < len(args); i++ {
		arg := args[i]
		argWithoutPrefix := strings.TrimPrefix(arg, "--")

		if util.ListContainsElement(MULTI_MODULE_COMMANDS, arg) {
			// Skip multi-module commands entirely
			continue
		}

		if util.ListContainsElement(ALL_TERRAGRUNT_STRING_OPTS, argWithoutPrefix) {
			// String flags have the argument and the value, so skip both
			i = i + 1
			continue
		}
		if util.ListContainsElement(ALL_TERRAGRUNT_BOOLEAN_OPTS, argWithoutPrefix) {
			// Just skip the boolean flag
			continue
		}

		out = append(out, arg)
	}
	return out
}

// Find a boolean argument (e.g. --foo) of the given name in the given list of arguments. If it's present, return true.
// If it isn't, return defaultValue.
func parseBooleanArg(args []string, argName string, defaultValue bool) bool {
	for _, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			return true
		}
	}
	return defaultValue
}

// Find a string argument (e.g. --foo "VALUE") of the given name in the given list of arguments. If it's present,
// return its value. If it is present, but has no value, return an error. If it isn't present, return defaultValue.
func parseStringArg(args []string, argName string, defaultValue string) (string, error) {
	for i, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			if (i + 1) < len(args) {
				return args[i+1], nil
			} else {
				return "", errors.WithStackTrace(ArgMissingValue(argName))
			}
		}
	}
	return defaultValue, nil
}

// Find multiple string arguments of the same type (e.g. --foo "VALUE_A" --foo "VALUE_B") of the given name in the given list of arguments. If there are any present,
// return a list of all values. If there are any present, but one of them has no value, return an error. If there aren't any present, return defaultValue.
func parseMultiStringArg(args []string, argName string, defaultValue []string) ([]string, error) {
	stringArgs := []string{}

	for i, arg := range args {
		if arg == fmt.Sprintf("--%s", argName) {
			if (i + 1) < len(args) {
				stringArgs = append(stringArgs, args[i+1])
			} else {
				return nil, errors.WithStackTrace(ArgMissingValue(argName))
			}
		}
	}
	if len(stringArgs) == 0 {
		return defaultValue, nil
	}

	return stringArgs, nil
}

// Custom error types

type ArgMissingValue string

func (err ArgMissingValue) Error() string {
	return fmt.Sprintf("You must specify a value for the --%s option", string(err))
}
<|endoftext|>


<commit_before>package sieve

import "math"

// Eratosthenes is Sieve of Eratosthenes
func Eratosthenes(n int) []int {
	if n < 2 {
		return []int{}
	}

	r := int(math.Floor(math.Sqrt(float64(n))))
	list := make([]bool, n+1)
	list[0], list[1] = true, true

	for i := 2; i <= r; i++ {
		if !list[i] {
			for j := i * i; j <= n; j += i {
				list[j] = true
			}
		}
	}

	l := n / int(math.Floor(math.Log(float64(n))))
	primes := make([]int, 0, l)
	for i, v := range list {
		if v {
			continue
		}
		primes = append(primes, i)
	}

	return primes
}
<commit_msg>Fix Floor -> Ceil<commit_after>package sieve

import "math"

// Eratosthenes is Sieve of Eratosthenes
func Eratosthenes(n int) []int {
	if n < 2 {
		return []int{}
	}

	r := int(math.Floor(math.Sqrt(float64(n))))
	list := make([]bool, n+1)
	list[0], list[1] = true, true

	for i := 2; i <= r; i++ {
		if !list[i] {
			for j := i * i; j <= n; j += i {
				list[j] = true
			}
		}
	}

	l := n / int(math.Ceil(math.Log(float64(n))))
	primes := make([]int, 0, l)
	for i, v := range list {
		if v {
			continue
		}
		primes = append(primes, i)
	}

	return primes
}
<|endoftext|>


<commit_before>package simple

import (
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
	"testing"
)

func TestNew(t *testing.T) {
	r := New("http://example.com/")
	assert.Equal(t, "http://example.com/", r.URL)
}

func TestNewVU(t *testing.T) {
	r := New("http://example.com/")
	vu, err := r.NewVU()
	assert.NoError(t, err)
	assert.IsType(t, &VU{}, vu)
}

func TestReconfigure(t *testing.T) {
	r := New("http://example.com/")

	vu, err := r.NewVU()
	assert.NoError(t, err)

	err = vu.Reconfigure(12345)
	assert.NoError(t, err)
}

func TestRunOnceReportsStats(t *testing.T) {
	r := New("http://255.255.255.255/")
	vu, err := r.NewVU()
	assert.NoError(t, err)

	err = vu.RunOnce(context.Background())
	assert.Error(t, err)

	mRequestsFound := false
	mErrrosFound := false
	for _, p := range vu.(*VU).Collector.Batch {
		switch p.Stat {
		case &mRequests:
			mRequestsFound = true
			assert.Contains(t, p.Tags, "url")
			assert.Contains(t, p.Tags, "method")
			assert.Contains(t, p.Tags, "status")
			assert.Contains(t, p.Values, "duration")
		case &mErrors:
			mErrrosFound = true
			assert.Contains(t, p.Tags, "url")
			assert.Contains(t, p.Tags, "method")
			assert.Contains(t, p.Tags, "status")
			assert.Contains(t, p.Values, "value")
		}
	}
	assert.True(t, mRequestsFound)
	assert.True(t, mErrrosFound)
}
<commit_msg>[typo] The heck is an errro<commit_after>package simple

import (
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
	"testing"
)

func TestNew(t *testing.T) {
	r := New("http://example.com/")
	assert.Equal(t, "http://example.com/", r.URL)
}

func TestNewVU(t *testing.T) {
	r := New("http://example.com/")
	vu, err := r.NewVU()
	assert.NoError(t, err)
	assert.IsType(t, &VU{}, vu)
}

func TestReconfigure(t *testing.T) {
	r := New("http://example.com/")

	vu, err := r.NewVU()
	assert.NoError(t, err)

	err = vu.Reconfigure(12345)
	assert.NoError(t, err)
}

func TestRunOnceReportsStats(t *testing.T) {
	r := New("http://255.255.255.255/")
	vu, err := r.NewVU()
	assert.NoError(t, err)

	err = vu.RunOnce(context.Background())
	assert.Error(t, err)

	mRequestsFound := false
	mErrorsFound := false
	for _, p := range vu.(*VU).Collector.Batch {
		switch p.Stat {
		case &mRequests:
			mRequestsFound = true
			assert.Contains(t, p.Tags, "url")
			assert.Contains(t, p.Tags, "method")
			assert.Contains(t, p.Tags, "status")
			assert.Contains(t, p.Values, "duration")
		case &mErrors:
			mErrorsFound = true
			assert.Contains(t, p.Tags, "url")
			assert.Contains(t, p.Tags, "method")
			assert.Contains(t, p.Tags, "status")
			assert.Contains(t, p.Values, "value")
		}
	}
	assert.True(t, mRequestsFound)
	assert.True(t, mErrorsFound)
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package invite provides the JSON handlers to send invitations to gonawin app.
package invite

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"appengine"
	"appengine/taskqueue"

	"github.com/santiaago/gonawin/helpers"
	"github.com/santiaago/gonawin/helpers/log"
	templateshlp "github.com/santiaago/gonawin/helpers/templates"

	mdl "github.com/santiaago/gonawin/models"
)

const inviteMessage = `
Hi there,
Join us at gonawin.

You will be able to join your friends and compete with them by predicting the results of your favorite sports events!

Sign in here: %s

Have fun,
Your friends @ Gonawin

`

// invite json handler
func Invite(w http.ResponseWriter, r *http.Request, u *mdl.User) error {
	desc := "invite handler:"
	c := appengine.NewContext(r)

	if r.Method == "POST" {
		emailsList := r.FormValue("emails")

		if len(emailsList) <= 0 {
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeInviteNoEmailAddr)}
		}
		splitemails := strings.Split(emailsList, ",")
		// remove leading and trailing spaces from each email.
		emails := make([]string, 0)
		for _, e := range splitemails {
			emails = append(emails, strings.Trim(e, " "))
		}

		// validate emails
		if !helpers.AreEmailsValid(emails) {
			log.Errorf(c, "%s emails not valid dude!", desc, emails)
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeInviteEmailsInvalid)}
		}

		currenturl := fmt.Sprintf("http://%s/#", r.Host)
		body := fmt.Sprintf(inviteMessage, currenturl)

		bname, errname := json.Marshal(u.Name)
		if errname != nil {
			log.Errorf(c, "%s Error marshaling", desc, errname)
		}

		bbody, errbody := json.Marshal(body)
		if errbody != nil {
			log.Errorf(c, "%s Error marshaling", desc, errbody)
		}

		for _, email := range emails {

			bemail, errm := json.Marshal(email)
			if errm != nil {
				log.Errorf(c, "%s Error marshaling", desc, errm)
			}

			task := taskqueue.NewPOSTTask("/a/invite/", url.Values{
				"email": []string{string(bemail)},
				"name":  []string{string(bname)},
				"body":  []string{string(bbody)},
			})
			if _, err := taskqueue.Add(c, task, ""); err != nil {
				log.Errorf(c, "%s unable to add task to taskqueue.", desc)
				return err
			} else {
				log.Infof(c, "%s add task to taskqueue successfully", desc)
			}
		}
		msg := fmt.Sprintf("Email invitations have been successfully sent.")
		data := struct {
			MessageInfo string `json:",omitempty"`
		}{
			msg,
		}

		return templateshlp.RenderJson(w, c, data)
	}
	return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
}
<commit_msg>invite invite handler - exit first<commit_after>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package invite provides the JSON handlers to send invitations to gonawin app.
package invite

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"appengine"
	"appengine/taskqueue"

	"github.com/santiaago/gonawin/helpers"
	"github.com/santiaago/gonawin/helpers/log"
	templateshlp "github.com/santiaago/gonawin/helpers/templates"

	mdl "github.com/santiaago/gonawin/models"
)

const inviteMessage = `
Hi there,
Join us at gonawin.

You will be able to join your friends and compete with them by predicting the results of your favorite sports events!

Sign in here: %s

Have fun,
Your friends @ Gonawin

`

// Invite handler, use it to invite users to use gonawin.
func Invite(w http.ResponseWriter, r *http.Request, u *mdl.User) error {
	if r.Method != "POST" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	desc := "invite handler:"
	c := appengine.NewContext(r)

	emailsList := r.FormValue("emails")

	if len(emailsList) <= 0 {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeInviteNoEmailAddr)}
	}
	splitemails := strings.Split(emailsList, ",")

	// remove leading and trailing spaces from each email.
	emails := make([]string, 0)
	for _, e := range splitemails {
		emails = append(emails, strings.Trim(e, " "))
	}

	// validate emails
	if !helpers.AreEmailsValid(emails) {
		log.Errorf(c, "%s emails not valid dude!", desc, emails)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeInviteEmailsInvalid)}
	}

	currenturl := fmt.Sprintf("http://%s/#", r.Host)
	body := fmt.Sprintf(inviteMessage, currenturl)

	bname, errname := json.Marshal(u.Name)
	if errname != nil {
		log.Errorf(c, "%s Error marshaling", desc, errname)
	}

	bbody, errbody := json.Marshal(body)
	if errbody != nil {
		log.Errorf(c, "%s Error marshaling", desc, errbody)
	}

	for _, email := range emails {

		bemail, errm := json.Marshal(email)
		if errm != nil {
			log.Errorf(c, "%s Error marshaling", desc, errm)
		}

		task := taskqueue.NewPOSTTask("/a/invite/", url.Values{
			"email": []string{string(bemail)},
			"name":  []string{string(bname)},
			"body":  []string{string(bbody)},
		})
		if _, err := taskqueue.Add(c, task, ""); err != nil {
			log.Errorf(c, "%s unable to add task to taskqueue.", desc)
			return err
		} else {
			log.Infof(c, "%s add task to taskqueue successfully", desc)
		}
	}
	msg := fmt.Sprintf("Email invitations have been successfully sent.")
	data := struct {
		MessageInfo string `json:",omitempty"`
	}{
		msg,
	}

	return templateshlp.RenderJson(w, c, data)
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/gtfierro/cs262-project/common"
	"github.com/tinylib/msgp/msgp"
	"net"
	"sync"
)

var emptyList = []common.UUID{}

// Handles the subscriptions, updates, forwarding
type Broker struct {
	metadata *MetadataStore

	// map queries to clients
	subscriber_lock sync.RWMutex
	subscribers     map[string]clientList

	// map of producer ids to queries
	forwarding_lock sync.RWMutex
	forwarding      map[common.UUID]*queryList

	// index of producers
	producers_lock sync.RWMutex
	producers      map[common.UUID]*Producer

	// map query string to query struct
	query_lock sync.RWMutex
	queries    map[string]*Query

	// dead client notification
	killClient chan *Client
}

//TODO: config for broker?
func NewBroker(metadata *MetadataStore) *Broker {
	b := &Broker{
		metadata:    metadata,
		subscribers: make(map[string]clientList),
		forwarding:  make(map[common.UUID]*queryList),
		producers:   make(map[common.UUID]*Producer),
		queries:     make(map[string]*Query),
		killClient:  make(chan *Client),
	}

	go func(b *Broker) {
		for deadClient := range b.killClient {
			log.WithFields(log.Fields{
				"client": deadClient,
			}).Info("Removing dead client")
			b.subscriber_lock.Lock()
			for _, cl := range b.subscribers {
				cl.removeClient(deadClient)
			}
			b.subscriber_lock.Unlock()
		}
	}(b)

	return b
}

// safely adds entry to map[query][]Client map
func (b *Broker) mapQueryToClient(query string, c *Client) {
	b.subscriber_lock.Lock()
	if list, found := b.subscribers[query]; found {
		list.addClient(c)
	} else {
		// otherwise, create a new list with us in it
		b.subscribers[query] = clientList{c}
	}
	b.subscriber_lock.Unlock()
}

func (b *Broker) updateForwardingTable(query *Query) {
	b.forwarding_lock.Lock()
	query.RLock()
Loop:
	for producerID, _ := range query.MatchingProducers {
		if list, found := b.forwarding[producerID]; found {
			// check if we are already in the list
			for _, q2 := range list.queries {
				if q2 == query.Query {
					continue Loop
				}
			}
			log.WithFields(log.Fields{
				"producerID": producerID, "query": query.Query, "list": list,
			}).Debug("Adding query to existing query list")
			b.forwarding[producerID].addQuery(query.Query)
		} else {
			log.WithFields(log.Fields{
				"producerID": producerID, "query": query.Query,
			}).Debug("Adding query to NEW query list")
			b.forwarding[producerID] = &queryList{queries: []string{query.Query}}
		}
	}
	log.Debugf("forwarding table %v", b.forwarding)
	query.RUnlock()
	b.forwarding_lock.Unlock()
}

// we have the list of new and removed UUIDs for a query,
// so we update the forwarding table to match that
func (b *Broker) updateForwardingDiffs(query *Query, added, removed []common.UUID) {
	var (
		list  *queryList
		found bool
	)
	if len(removed) > 0 {
		b.forwarding_lock.Lock()
		for _, rm_uuid := range removed {
			if list, found = b.forwarding[rm_uuid]; !found {
				// no subscribers for this uuid
				continue
			}
			for _, tmp_query := range list.queries {
				if tmp_query == query.Query {
					list.removeQuery(query.Query)
					continue
				}
			}
		}
		b.forwarding_lock.Unlock()
	}
	b.updateForwardingTable(query)
}

func (b *Broker) ForwardMessage(m *common.PublishMessage) {
	var (
		matchingQueries *queryList
		found           bool
	)
	log.Debugf("forwarding msg? %v", m)
	b.forwarding_lock.RLock()
	// return if we can't find anyone to forward to
	matchingQueries, found = b.forwarding[m.UUID]
	b.forwarding_lock.RUnlock()

	if !found || matchingQueries.empty() {
		log.Debugf("no forwarding targets")
		return
	}

	var clientList []*Client
	for _, query := range matchingQueries.queries {
		b.subscriber_lock.RLock()
		clientList, found = b.subscribers[query]
		b.subscriber_lock.RUnlock()
		if !found || len(clientList) == 0 {
			log.Debugf("found no clients")
			break
		}
		for _, client := range clientList {
			go client.Send(m)
		}
	}
}

func (b *Broker) SendSubscriptionDiffs(query string, added, removed []common.UUID) {
	// if we don't do this, then empty lists show up as None
	// when we pack them
	if len(added) == 0 {
		added = emptyList
	}
	if len(removed) == 0 {
		removed = emptyList
	}
	if len(added) == 0 && len(removed) == 0 {
		return
	}
	msg := common.SubscriptionDiffMessage{"New": added, "Del": removed}
	// send to subscribers
	b.subscriber_lock.RLock()
	subscribers := b.subscribers[query]
	b.subscriber_lock.RUnlock()
	for _, sub := range subscribers {
		go sub.Send(&msg)
	}
}

// Evaluates the query and establishes the forwarding decisions.
// Returns the client
func (b *Broker) NewSubscription(querystring string, conn net.Conn) *Client {
	var (
		query *Query
		found bool
		err   error
	)
	b.query_lock.RLock()
	query, found = b.queries[querystring]
	b.query_lock.RUnlock()

	if !found {
		// parse it!
		queryAST := Parse(querystring)
		query, err = b.metadata.Query(queryAST)
		if err != nil {
			log.WithFields(log.Fields{
				"error": err, "query": querystring,
			}).Error("Error evaluating mongo query")
		}

		// add to our map of queries
		b.query_lock.Lock()
		b.queries[querystring] = query
		b.query_lock.Unlock()

		log.WithFields(log.Fields{
			"query": querystring, "results": query.MatchingProducers,
		}).Debug("Evaluated query")
	}

	c := NewClient(querystring, &conn, b.killClient)

	// set up forwarding for all initial producers
	b.updateForwardingTable(query)
	b.mapQueryToClient(querystring, c)
	msg := common.MatchingProducersMessage(query.MatchingProducers)
	c.Send(&msg)

	return c
}

func (b *Broker) HandleProducer(msg *common.PublishMessage, dec *msgp.Reader, conn net.Conn) {
	// use uuid to find old producer or create new one
	// add producer.C to a list of channels to select from
	// when we receive a message from a producer, save the
	// metadata and evaluate it, then forward it.
	// decode the incoming message
	var (
		err   error
		found bool
		p     *Producer
	)

	// save the metadata
	err = b.metadata.Save(msg)
	if err != nil {
		log.WithFields(log.Fields{
			"message": msg, "error": err,
		}).Error("Could not save metadata")
		conn.Close()
		return
	}

	// find the producer
	b.producers_lock.RLock()
	p, found = b.producers[msg.UUID]
	b.producers_lock.RUnlock()
	if !found {
		p = NewProducer(msg.UUID, dec)
	}
	b.RemapProducer(p, msg)

	// queue first message to be sent
	b.ForwardMessage(msg)

	go func(p *Producer) {
		for p.C != nil {
			select {
			case <-p.stop:
				return
			case msg := <-p.C:
				msg.L.RLock()
				if len(msg.Metadata) > 0 {
					err = b.metadata.Save(msg)
					b.RemapProducer(p, msg)
				}
				b.ForwardMessage(msg)
				msg.L.RUnlock()
			}
		}
	}(p)
}

// 1. Firstly, have a method that given a producer (which is an implicit pointer to its
//    metadata) reevaluates all queries.

// when we receive a new producer, find related queries and update the forwarding
// tables that match. If newMetadata is nil, it will just reevaluate using current
// producer metadata across *all* queries, else it can use metadata in the provided
// Message to filter which queries to reevaluate
func (b *Broker) RemapProducer(p *Producer, newMetadata *common.PublishMessage) {
	//TODO: make a list of queries to update so that its unique THEN actually reevaluate
	//      to get added/removed lists. Once we have added/removed lists we use
	//      that to update the forwarding table.
	//      Question: Do i save the loop over to make OLD until after the forwarding table
	//      is updated? NO this should be encapsulated?
	var queriesToReevaluate = make(map[string]*Query)
	// TODO: remove this TRUE statement when we implement key-informed reevaluation
	if newMetadata == nil || true { // reevaluate ALL queries
		b.subscriber_lock.RLock()
		for querystring, _ := range b.subscribers {
			if _, found := queriesToReevaluate[querystring]; !found {
				b.query_lock.RLock()
				queriesToReevaluate[querystring] = b.queries[querystring]
				b.query_lock.RUnlock()
			}
		}
		b.subscriber_lock.RUnlock()

		for _, query := range queriesToReevaluate {
			added, removed := b.metadata.Reevaluate(query)
			log.WithFields(log.Fields{
				"query": query.Query, "added": added, "removed": removed,
			}).Info("Reevaluated query")
			b.updateForwardingDiffs(query, added, removed)
			b.SendSubscriptionDiffs(query.Query, added, removed)
		}
	}
}
<commit_msg>implement key-based reevalation -- might still need a little bit of testing<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/gtfierro/cs262-project/common"
	"github.com/tinylib/msgp/msgp"
	"net"
	"sync"
)

var emptyList = []common.UUID{}

// Handles the subscriptions, updates, forwarding
type Broker struct {
	metadata *MetadataStore

	// map queries to clients
	subscriber_lock sync.RWMutex
	subscribers     map[string]clientList

	// map of producer ids to queries
	forwarding_lock sync.RWMutex
	forwarding      map[common.UUID]*queryList

	// index of producers
	producers_lock sync.RWMutex
	producers      map[common.UUID]*Producer

	// map query string to query struct
	query_lock sync.RWMutex
	queries    map[string]*Query

	// map metadata key to list of queries involving that key
	key_lock sync.RWMutex
	keys     map[string]*queryList

	// dead client notification
	killClient chan *Client
}

//TODO: config for broker?
func NewBroker(metadata *MetadataStore) *Broker {
	b := &Broker{
		metadata:    metadata,
		subscribers: make(map[string]clientList),
		forwarding:  make(map[common.UUID]*queryList),
		producers:   make(map[common.UUID]*Producer),
		queries:     make(map[string]*Query),
		keys:        make(map[string]*queryList),
		killClient:  make(chan *Client),
	}

	go func(b *Broker) {
		for deadClient := range b.killClient {
			log.WithFields(log.Fields{
				"client": deadClient,
			}).Info("Removing dead client")
			b.subscriber_lock.Lock()
			for _, cl := range b.subscribers {
				cl.removeClient(deadClient)
			}
			b.subscriber_lock.Unlock()
		}
	}(b)

	return b
}

// safely adds entry to map[query][]Client map
func (b *Broker) mapQueryToClient(query string, c *Client) {
	b.subscriber_lock.Lock()
	if list, found := b.subscribers[query]; found {
		list.addClient(c)
	} else {
		// otherwise, create a new list with us in it
		b.subscribers[query] = clientList{c}
	}
	b.subscriber_lock.Unlock()
}

func (b *Broker) updateForwardingTable(query *Query) {
	b.forwarding_lock.Lock()
	query.RLock()
Loop:
	for producerID, _ := range query.MatchingProducers {
		if list, found := b.forwarding[producerID]; found {
			// check if we are already in the list
			for _, q2 := range list.queries {
				if q2 == query.Query {
					continue Loop
				}
			}
			log.WithFields(log.Fields{
				"producerID": producerID, "query": query.Query, "list": list,
			}).Debug("Adding query to existing query list")
			b.forwarding[producerID].addQuery(query.Query)
		} else {
			log.WithFields(log.Fields{
				"producerID": producerID, "query": query.Query,
			}).Debug("Adding query to NEW query list")
			b.forwarding[producerID] = &queryList{queries: []string{query.Query}}
		}
	}
	log.Debugf("forwarding table %v", b.forwarding)
	query.RUnlock()
	b.forwarding_lock.Unlock()
}

// we have the list of new and removed UUIDs for a query,
// so we update the forwarding table to match that
func (b *Broker) updateForwardingDiffs(query *Query, added, removed []common.UUID) {
	var (
		list  *queryList
		found bool
	)
	if len(removed) > 0 {
		b.forwarding_lock.Lock()
		for _, rm_uuid := range removed {
			if list, found = b.forwarding[rm_uuid]; !found {
				// no subscribers for this uuid
				continue
			}
			for _, tmp_query := range list.queries {
				if tmp_query == query.Query {
					list.removeQuery(query.Query)
					continue
				}
			}
		}
		b.forwarding_lock.Unlock()
	}
	b.updateForwardingTable(query)
}

func (b *Broker) ForwardMessage(m *common.PublishMessage) {
	var (
		matchingQueries *queryList
		found           bool
	)
	log.Debugf("forwarding msg? %v", m)
	b.forwarding_lock.RLock()
	// return if we can't find anyone to forward to
	matchingQueries, found = b.forwarding[m.UUID]
	b.forwarding_lock.RUnlock()

	if !found || matchingQueries.empty() {
		log.Debugf("no forwarding targets")
		return
	}

	var clientList []*Client
	for _, query := range matchingQueries.queries {
		b.subscriber_lock.RLock()
		clientList, found = b.subscribers[query]
		b.subscriber_lock.RUnlock()
		if !found || len(clientList) == 0 {
			log.Debugf("found no clients")
			break
		}
		for _, client := range clientList {
			go client.Send(m)
		}
	}
}

func (b *Broker) SendSubscriptionDiffs(query string, added, removed []common.UUID) {
	// if we don't do this, then empty lists show up as None
	// when we pack them
	if len(added) == 0 {
		added = emptyList
	}
	if len(removed) == 0 {
		removed = emptyList
	}
	if len(added) == 0 && len(removed) == 0 {
		return
	}
	msg := common.SubscriptionDiffMessage{"New": added, "Del": removed}
	// send to subscribers
	b.subscriber_lock.RLock()
	subscribers := b.subscribers[query]
	b.subscriber_lock.RUnlock()
	for _, sub := range subscribers {
		go sub.Send(&msg)
	}
}

// Evaluates the query and establishes the forwarding decisions.
// Returns the client
func (b *Broker) NewSubscription(querystring string, conn net.Conn) *Client {
	var (
		query *Query
		found bool
		err   error
	)
	b.query_lock.RLock()
	query, found = b.queries[querystring]
	b.query_lock.RUnlock()

	if !found {
		// parse it!
		queryAST := Parse(querystring)
		query, err = b.metadata.Query(queryAST)
		if err != nil {
			log.WithFields(log.Fields{
				"error": err, "query": querystring,
			}).Error("Error evaluating mongo query")
		}

		// add to our map of queries
		b.query_lock.Lock()
		b.queries[querystring] = query
		b.query_lock.Unlock()

		// add the mapping of key -> query
		b.key_lock.Lock()
		for _, key := range query.Keys {
			if list, found := b.keys[key]; found {
				list.addQuery(querystring)
			} else {
				b.keys[key] = &queryList{queries: []string{querystring}}
			}
		}
		b.key_lock.Unlock()

		log.WithFields(log.Fields{
			"query": querystring, "results": query.MatchingProducers,
		}).Debug("Evaluated query")
	}

	c := NewClient(querystring, &conn, b.killClient)

	// set up forwarding for all initial producers
	b.updateForwardingTable(query)
	b.mapQueryToClient(querystring, c)
	msg := common.MatchingProducersMessage(query.MatchingProducers)
	c.Send(&msg)

	return c
}

func (b *Broker) HandleProducer(msg *common.PublishMessage, dec *msgp.Reader, conn net.Conn) {
	// use uuid to find old producer or create new one
	// add producer.C to a list of channels to select from
	// when we receive a message from a producer, save the
	// metadata and evaluate it, then forward it.
	// decode the incoming message
	var (
		err   error
		found bool
		p     *Producer
	)

	// save the metadata
	err = b.metadata.Save(msg)
	if err != nil {
		log.WithFields(log.Fields{
			"message": msg, "error": err,
		}).Error("Could not save metadata")
		conn.Close()
		return
	}

	// find the producer
	b.producers_lock.RLock()
	p, found = b.producers[msg.UUID]
	b.producers_lock.RUnlock()
	if !found {
		p = NewProducer(msg.UUID, dec)
	}
	b.RemapProducer(p, msg)

	// queue first message to be sent
	b.ForwardMessage(msg)

	go func(p *Producer) {
		for p.C != nil {
			select {
			case <-p.stop:
				return
			case msg := <-p.C:
				msg.L.RLock()
				if len(msg.Metadata) > 0 {
					err = b.metadata.Save(msg)
					b.RemapProducer(p, msg)
				}
				b.ForwardMessage(msg)
				msg.L.RUnlock()
			}
		}
	}(p)
}

// 1. Firstly, have a method that given a producer (which is an implicit pointer to its
//    metadata) reevaluates all queries.

// when we receive a new producer, find related queries and update the forwarding
// tables that match. If message is nil, it will just reevaluate using current
// producer metadata across *all* queries, else it can use metadata in the provided
// Message to filter which queries to reevaluate
func (b *Broker) RemapProducer(p *Producer, message *common.PublishMessage) {
	var queriesToReevaluate = make(map[string]*Query)
	if message == nil { // reevaluate ALL queries
		log.Debug("Reevaluating all queries")
		b.subscriber_lock.RLock()
		b.query_lock.RLock()
		for querystring, _ := range b.subscribers {
			if _, found := queriesToReevaluate[querystring]; !found {
				queriesToReevaluate[querystring] = b.queries[querystring]
			}
		}
		b.subscriber_lock.RUnlock()
		b.query_lock.RUnlock()
		goto reevaluate
	}
	// if we have new metadata
	b.key_lock.RLock()
	b.query_lock.RLock()
	b.subscriber_lock.RLock()
	// loop through each of the metadata keys
	log.Debugf("Reevaluting w/ metadata %v", message.Metadata)
	for key, _ := range message.Metadata {
		// pull out the list of affected queries
		list, found := b.keys[key]
		log.Debugf("For key %v found queries %v", key, list)
		if !found { // if there are no affected queries, go on to the next key
			continue
		}
		// for each query in the found list
		for _, querystring := range list.queries {
			// if there are no subscribers for this query, continue
			if subscribers, found := b.subscribers[querystring]; !found || len(subscribers) == 0 {
				continue
			}
			if _, found := queriesToReevaluate[querystring]; !found {
				queriesToReevaluate[querystring] = b.queries[querystring]
			}
		}
	}
	b.subscriber_lock.RUnlock()
	b.query_lock.RUnlock()
	b.key_lock.RUnlock()

reevaluate:
	for _, query := range queriesToReevaluate {
		added, removed := b.metadata.Reevaluate(query)
		log.WithFields(log.Fields{
			"query": query.Query, "added": added, "removed": removed,
		}).Info("Reevaluated query")
		b.updateForwardingDiffs(query, added, removed)
		b.SendSubscriptionDiffs(query.Query, added, removed)
	}
}
<|endoftext|>