<commit_before>/*
Copyright 2019 The Vitess 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 schema

import (
	"testing"
)

func TestParseAlterTableOptions(t *testing.T) {
	type expect struct {
		schema, table, options string
	}
	tests := map[string]expect{
		"add column i int, drop column d":                               {schema: "", table: "", options: "add column i int, drop column d"},
		"  add column i int, drop column d  ":                           {schema: "", table: "", options: "add column i int, drop column d"},
		"alter table t add column i int, drop column d":                 {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter    table   t      add column i int, drop column d":       {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter table `t` add column i int, drop column d":               {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter table `scm`.`t` add column i int, drop column d":         {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table `scm`.t add column i int, drop column d":           {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table scm.`t` add column i int, drop column d":           {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table scm.t add column i int, drop column d":             {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"  alter       table   scm.`t` add column i int, drop column d": {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter  table scm.t add column i int, drop column d":            {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"ALTER TABLE scm.t ADD COLUMN i int, DROP COLUMN d":             {schema: "scm", table: "t", options: "ADD COLUMN i int, DROP COLUMN d"},
	}
	for query, expect := range tests {
		schema, table, options := ParseAlterTableOptions(query)
		if schema != expect.schema {
			t.Errorf("schema: %+v, want:%+v", schema, expect.schema)
		}
		if table != expect.table {
			t.Errorf("table: %+v, want:%+v", table, expect.table)
		}
		if options != expect.options {
			t.Errorf("options: %+v, want:%+v", options, expect.options)
		}
	}
}
<commit_msg>minor update to parser_test<commit_after>/*
Copyright 2019 The Vitess 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 schema

import (
	"testing"
)

func TestParseAlterTableOptions(t *testing.T) {
	type expect struct {
		schema, table, options string
	}
	tests := map[string]expect{
		"add column i int, drop column d":                               {schema: "", table: "", options: "add column i int, drop column d"},
		"  add column i int, drop column d  ":                           {schema: "", table: "", options: "add column i int, drop column d"},
		"alter table t add column i int, drop column d":                 {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter    table   t      add column i int, drop column d":       {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter table `t` add column i int, drop column d":               {schema: "", table: "t", options: "add column i int, drop column d"},
		"alter table `scm`.`t` add column i int, drop column d":         {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table `scm`.t add column i int, drop column d":           {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table scm.`t` add column i int, drop column d":           {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"alter table scm.t add column i int, drop column d":             {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"  alter       table   scm.`t` add column i int, drop column d": {schema: "scm", table: "t", options: "add column i int, drop column d"},
		"ALTER  table scm.t ADD COLUMN i int, DROP COLUMN d":            {schema: "scm", table: "t", options: "ADD COLUMN i int, DROP COLUMN d"},
		"ALTER TABLE scm.t ADD COLUMN i int, DROP COLUMN d":             {schema: "scm", table: "t", options: "ADD COLUMN i int, DROP COLUMN d"},
	}
	for query, expect := range tests {
		schema, table, options := ParseAlterTableOptions(query)
		if schema != expect.schema {
			t.Errorf("schema: %+v, want:%+v", schema, expect.schema)
		}
		if table != expect.table {
			t.Errorf("table: %+v, want:%+v", table, expect.table)
		}
		if options != expect.options {
			t.Errorf("options: %+v, want:%+v", options, expect.options)
		}
	}
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

// Compare only the relative path of two self links.
func compareSelfLinkRelativePaths(k, old, new string, d *schema.ResourceData) bool {
	oldStripped, err := getRelativePath(old)
	if err != nil {
		return false
	}

	newStripped, err := getRelativePath(new)
	if err != nil {
		return false
	}

	if oldStripped == newStripped {
		return true
	}

	return false
}

// Use this method when the field accepts either a name or a self_link referencing a resource.
// The value we store (i.e. `old` in this method), must be a self_link.
func compareSelfLinkOrResourceName(k, old, new string, d *schema.ResourceData) bool {
	newParts := strings.Split(new, "/")

	if len(newParts) == 1 {
		// `new` is a name
		// `old` is always a self_link
		if GetResourceNameFromSelfLink(old) == newParts[0] {
			return true
		}
	}

	// The `new` string is a self_link
	return compareSelfLinkRelativePaths(k, old, new, d)
}

// Hash the relative path of a self link.
func selfLinkRelativePathHash(selfLink interface{}) int {
	path, _ := getRelativePath(selfLink.(string))
	return hashcode.String(path)
}

func getRelativePath(selfLink string) (string, error) {
	stringParts := strings.SplitAfterN(selfLink, "projects/", 2)
	if len(stringParts) != 2 {
		return "", fmt.Errorf("String was not a self link: %s", selfLink)
	}

	return "projects/" + stringParts[1], nil
}

// Hash the name path of a self link.
func selfLinkNameHash(selfLink interface{}) int {
	name := GetResourceNameFromSelfLink(selfLink.(string))
	return hashcode.String(name)
}

func ConvertSelfLinkToV1(link string) string {
	reg := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/")
	return reg.ReplaceAllString(link, "/compute/v1/projects/")
}

func GetResourceNameFromSelfLink(link string) string {
	parts := strings.Split(link, "/")
	return parts[len(parts)-1]
}

func NameFromSelfLinkStateFunc(v interface{}) string {
	return GetResourceNameFromSelfLink(v.(string))
}

func StoreResourceName(resourceLink interface{}) string {
	return GetResourceNameFromSelfLink(resourceLink.(string))
}
<commit_msg>Clarifying helper method usage<commit_after>package google

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

// Compare only the relative path of two self links.
func compareSelfLinkRelativePaths(_, old, new string, _ *schema.ResourceData) bool {
	oldStripped, err := getRelativePath(old)
	if err != nil {
		return false
	}

	newStripped, err := getRelativePath(new)
	if err != nil {
		return false
	}

	if oldStripped == newStripped {
		return true
	}

	return false
}

// compareSelfLinkOrResourceName checks if two resources are the same resource
//
// Use this method when the field accepts either a name or a self_link referencing a resource.
// The value we store (i.e. `old` in this method), must be a self_link.
func compareSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool {
	newParts := strings.Split(new, "/")

	if len(newParts) == 1 {
		// `new` is a name
		// `old` is always a self_link
		if GetResourceNameFromSelfLink(old) == newParts[0] {
			return true
		}
	}

	// The `new` string is a self_link
	return compareSelfLinkRelativePaths("", old, new, nil)
}

// Hash the relative path of a self link.
func selfLinkRelativePathHash(selfLink interface{}) int {
	path, _ := getRelativePath(selfLink.(string))
	return hashcode.String(path)
}

func getRelativePath(selfLink string) (string, error) {
	stringParts := strings.SplitAfterN(selfLink, "projects/", 2)
	if len(stringParts) != 2 {
		return "", fmt.Errorf("String was not a self link: %s", selfLink)
	}

	return "projects/" + stringParts[1], nil
}

// Hash the name path of a self link.
func selfLinkNameHash(selfLink interface{}) int {
	name := GetResourceNameFromSelfLink(selfLink.(string))
	return hashcode.String(name)
}

func ConvertSelfLinkToV1(link string) string {
	reg := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/")
	return reg.ReplaceAllString(link, "/compute/v1/projects/")
}

func GetResourceNameFromSelfLink(link string) string {
	parts := strings.Split(link, "/")
	return parts[len(parts)-1]
}

func NameFromSelfLinkStateFunc(v interface{}) string {
	return GetResourceNameFromSelfLink(v.(string))
}

func StoreResourceName(resourceLink interface{}) string {
	return GetResourceNameFromSelfLink(resourceLink.(string))
}
<|endoftext|>


<commit_before>package google

import (
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

// Compare only the relative path of two self links.
func compareSelfLinkRelativePaths(_, old, new string, _ *schema.ResourceData) bool {
	oldStripped, err := getRelativePath(old)
	if err != nil {
		return false
	}

	newStripped, err := getRelativePath(new)
	if err != nil {
		return false
	}

	if oldStripped == newStripped {
		return true
	}

	return false
}

// compareSelfLinkOrResourceName checks if two resources are the same resource
//
// Use this method when the field accepts either a name or a self_link referencing a resource.
// The value we store (i.e. `old` in this method), must be a self_link.
func compareSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool {
	newParts := strings.Split(new, "/")

	if len(newParts) == 1 {
		// `new` is a name
		// `old` is always a self_link
		if GetResourceNameFromSelfLink(old) == newParts[0] {
			return true
		}
	}

	// The `new` string is a self_link
	return compareSelfLinkRelativePaths("", old, new, nil)
}

// Hash the relative path of a self link.
func selfLinkRelativePathHash(selfLink interface{}) int {
	path, _ := getRelativePath(selfLink.(string))
	return hashcode.String(path)
}

func getRelativePath(selfLink string) (string, error) {
	stringParts := strings.SplitAfterN(selfLink, "projects/", 2)
	if len(stringParts) != 2 {
		return "", fmt.Errorf("String was not a self link: %s", selfLink)
	}

	return "projects/" + stringParts[1], nil
}

// Hash the name path of a self link.
func selfLinkNameHash(selfLink interface{}) int {
	name := GetResourceNameFromSelfLink(selfLink.(string))
	return hashcode.String(name)
}

func ConvertSelfLinkToV1(link string) string {
	reg := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/")
	return reg.ReplaceAllString(link, "/compute/v1/projects/")
}

func GetResourceNameFromSelfLink(link string) string {
	parts := strings.Split(link, "/")
	return parts[len(parts)-1]
}

func NameFromSelfLinkStateFunc(v interface{}) string {
	return GetResourceNameFromSelfLink(v.(string))
}

func StoreResourceName(resourceLink interface{}) string {
	return GetResourceNameFromSelfLink(resourceLink.(string))
}

type LocationType int

const (
	Zonal LocationType = iota
	Regional
	Global
)

func GetZonalResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config) (string, string, string, error) {
	return getResourcePropertiesFromSelfLinkOrSchema(d, config, Zonal)
}

func GetRegionalResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config) (string, string, string, error) {
	return getResourcePropertiesFromSelfLinkOrSchema(d, config, Regional)
}

func getResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config, locationType LocationType) (string, string, string, error) {
	if selfLink, ok := d.GetOk("self_link"); ok {
		return GetLocationalResourcePropertiesFromSelfLinkString(selfLink.(string))
	} else {
		project, err := getProject(d, config)
		if err != nil {
			return "", "", "", err
		}

		location := ""
		if locationType == Regional {
			location, err = getRegion(d, config)
			if err != nil {
				return "", "", "", err
			}
		} else if locationType == Zonal {
			location, err = getZone(d, config)
			if err != nil {
				return "", "", "", err
			}
		}

		n, ok := d.GetOk("name")
		name := n.(string)
		if !ok {
			return "", "", "", errors.New("must provide either `self_link` or `name`")
		}
		return project, location, name, nil
	}
}

// given a full locational (non-global) self link, returns the project + region/zone + name or an error
func GetLocationalResourcePropertiesFromSelfLinkString(selfLink string) (string, string, string, error) {
	parsed, err := url.Parse(selfLink)
	if err != nil {
		return "", "", "", err
	}

	s := strings.Split(parsed.Path, "/")
	return s[4], s[6], s[8], nil
}

// return the region a selfLink is referring to
func GetRegionFromRegionSelfLink(selfLink string) string {
	re := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/[a-zA-Z0-9-]*/regions/([a-zA-Z0-9-]*)")
	switch {
	case re.MatchString(selfLink):
		if res := re.FindStringSubmatch(selfLink); len(res) == 2 && res[1] != "" {
			return res[1]
		}
	}
	return selfLink
}
<commit_msg>Error instead of panicking if self link isn't long enough (#261)<commit_after>package google

import (
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

// Compare only the relative path of two self links.
func compareSelfLinkRelativePaths(_, old, new string, _ *schema.ResourceData) bool {
	oldStripped, err := getRelativePath(old)
	if err != nil {
		return false
	}

	newStripped, err := getRelativePath(new)
	if err != nil {
		return false
	}

	if oldStripped == newStripped {
		return true
	}

	return false
}

// compareSelfLinkOrResourceName checks if two resources are the same resource
//
// Use this method when the field accepts either a name or a self_link referencing a resource.
// The value we store (i.e. `old` in this method), must be a self_link.
func compareSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool {
	newParts := strings.Split(new, "/")

	if len(newParts) == 1 {
		// `new` is a name
		// `old` is always a self_link
		if GetResourceNameFromSelfLink(old) == newParts[0] {
			return true
		}
	}

	// The `new` string is a self_link
	return compareSelfLinkRelativePaths("", old, new, nil)
}

// Hash the relative path of a self link.
func selfLinkRelativePathHash(selfLink interface{}) int {
	path, _ := getRelativePath(selfLink.(string))
	return hashcode.String(path)
}

func getRelativePath(selfLink string) (string, error) {
	stringParts := strings.SplitAfterN(selfLink, "projects/", 2)
	if len(stringParts) != 2 {
		return "", fmt.Errorf("String was not a self link: %s", selfLink)
	}

	return "projects/" + stringParts[1], nil
}

// Hash the name path of a self link.
func selfLinkNameHash(selfLink interface{}) int {
	name := GetResourceNameFromSelfLink(selfLink.(string))
	return hashcode.String(name)
}

func ConvertSelfLinkToV1(link string) string {
	reg := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/")
	return reg.ReplaceAllString(link, "/compute/v1/projects/")
}

func GetResourceNameFromSelfLink(link string) string {
	parts := strings.Split(link, "/")
	return parts[len(parts)-1]
}

func NameFromSelfLinkStateFunc(v interface{}) string {
	return GetResourceNameFromSelfLink(v.(string))
}

func StoreResourceName(resourceLink interface{}) string {
	return GetResourceNameFromSelfLink(resourceLink.(string))
}

type LocationType int

const (
	Zonal LocationType = iota
	Regional
	Global
)

func GetZonalResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config) (string, string, string, error) {
	return getResourcePropertiesFromSelfLinkOrSchema(d, config, Zonal)
}

func GetRegionalResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config) (string, string, string, error) {
	return getResourcePropertiesFromSelfLinkOrSchema(d, config, Regional)
}

func getResourcePropertiesFromSelfLinkOrSchema(d *schema.ResourceData, config *Config, locationType LocationType) (string, string, string, error) {
	if selfLink, ok := d.GetOk("self_link"); ok {
		return GetLocationalResourcePropertiesFromSelfLinkString(selfLink.(string))
	} else {
		project, err := getProject(d, config)
		if err != nil {
			return "", "", "", err
		}

		location := ""
		if locationType == Regional {
			location, err = getRegion(d, config)
			if err != nil {
				return "", "", "", err
			}
		} else if locationType == Zonal {
			location, err = getZone(d, config)
			if err != nil {
				return "", "", "", err
			}
		}

		n, ok := d.GetOk("name")
		name := n.(string)
		if !ok {
			return "", "", "", errors.New("must provide either `self_link` or `name`")
		}
		return project, location, name, nil
	}
}

// given a full locational (non-global) self link, returns the project + region/zone + name or an error
func GetLocationalResourcePropertiesFromSelfLinkString(selfLink string) (string, string, string, error) {
	parsed, err := url.Parse(selfLink)
	if err != nil {
		return "", "", "", err
	}

	s := strings.Split(parsed.Path, "/")

	// This is a pretty bad way to tell if this is a self link, but stops us
	// from accessing an index out of bounds and causing a panic. generally, we
	// expect bad values to be partial URIs and names, so this will catch them
	if len(s) < 9 {
		return "", "", "", fmt.Errorf("value %s was not a self link", selfLink)
	}

	return s[4], s[6], s[8], nil
}

// return the region a selfLink is referring to
func GetRegionFromRegionSelfLink(selfLink string) string {
	re := regexp.MustCompile("/compute/[a-zA-Z0-9]*/projects/[a-zA-Z0-9-]*/regions/([a-zA-Z0-9-]*)")
	switch {
	case re.MatchString(selfLink):
		if res := re.FindStringSubmatch(selfLink); len(res) == 2 && res[1] != "" {
			return res[1]
		}
	}
	return selfLink
}
<|endoftext|>


<commit_before>package memo

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"sync"
	"testing"
	"time"
)

// BPS is emulation download speed for url (bytes per second)
const BPS = 10

type slowReader struct {
	d time.Duration
	r io.Reader
}

func (sr slowReader) Read(p []byte) (int, error) {
	time.Sleep(sr.d)
	return sr.r.Read(p[:1])
}

func newReader(r io.Reader, bps int) io.Reader {
	delay := time.Second / time.Duration(bps)
	return slowReader{r: r, d: delay}
}

// httpGetBodyMock emulates time-bound read functions.
func httpGetBodyMock(str string, done <-chan struct{}) (interface{}, error) {
	slr := newReader(strings.NewReader(str), BPS) // slow reader from string
	buf := bufio.NewReader(slr)
	dst := make([]byte, 0) // full content from reader
Loop:
	for {
		select {
		case <-done:
			return nil, fmt.Errorf("cancellation error")
		default:
			n, err := buf.ReadByte()
			if err == io.EOF {
				break Loop
			}
			if err != nil {
				return nil, err
			}
			// dst.WriteByte(n)
			dst = append(dst, n)
		}
	}
	return dst, nil
}

func httpGetBody(url string, done <-chan struct{}) (interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}

var HTTPGetBody = httpGetBodyMock

func incomingURLs() <-chan string {
	ch := make(chan string)
	go func() {
		for _, url := range []string{
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
		} {
			ch <- url
		}
		close(ch)
	}()
	return ch
}

type M interface {
	Get(key string, done <-chan struct{}) (interface{}, error)
}

func Sequential(t *testing.T, m M) {
	for url := range incomingURLs() {
		start := time.Now()
		value, err := m.Get(url, nil)
		if err != nil {
			log.Print(err)
			continue
		}
		fmt.Printf("%s, %s, %d bytes\n",
			url, time.Since(start), len(value.([]byte)))
	}
}

func Concurrent(t *testing.T, m M) {
	var n sync.WaitGroup
	done := make(chan struct{})
	go func() {
		<-time.After(time.Millisecond)
		close(done)
	}()
	for url := range incomingURLs() {
		n.Add(1)
		go func(url string) {
			defer n.Done()
			start := time.Now()
			value, err := m.Get(url, done)
			if err != nil {
				log.Print(err)
				return
			}
			fmt.Printf("%s, %s, %d bytes\n",
				url, time.Since(start), len(value.([]byte)))
		}(url)
	}
	n.Wait()
}

func TestSequential(t *testing.T) {
	m := New(HTTPGetBody)
	defer m.Close()
	Sequential(t, m)
}

func TestConcurrent(t *testing.T) {
	m := New(HTTPGetBody)
	defer m.Close()
	Concurrent(t, m)
}
<commit_msg>[9.3] draft test for cancellation<commit_after>package memo

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"sync"
	"testing"
	"time"
)

// BPS is emulation download speed for url (bytes per second)
const BPS = 100

type slowReader struct {
	d time.Duration
	r io.Reader
}

func (sr slowReader) Read(p []byte) (int, error) {
	time.Sleep(sr.d)
	return sr.r.Read(p[:1])
}

func newReader(r io.Reader, bps int) io.Reader {
	delay := time.Second / time.Duration(bps)
	return slowReader{r: r, d: delay}
}

var errCancel = errors.New("cancellation error")

// httpGetBodyMock emulates time-bound read functions.
func httpGetBodyMock(str string, done <-chan struct{}) (interface{}, error) {
	slr := newReader(strings.NewReader(str), BPS) // slow reader from string
	buf := bufio.NewReader(slr)
	dst := make([]byte, 0) // full content from reader
Loop:
	for {
		select {
		case <-done:
			return nil, errCancel
		default:
			n, err := buf.ReadByte()
			if err == io.EOF {
				break Loop
			}
			if err != nil {
				return nil, err
			}
			// dst.WriteByte(n)
			dst = append(dst, n)
		}
	}
	return dst, nil
}

func httpGetBody(url string, done <-chan struct{}) (interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}

var HTTPGetBody = httpGetBodyMock

func incomingURLs() <-chan string {
	ch := make(chan string)
	go func() {
		for _, url := range []string{
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
		} {
			ch <- url
		}
		close(ch)
	}()
	return ch
}

type M interface {
	Get(key string, done <-chan struct{}) (interface{}, error)
}

func Sequential(t *testing.T, m M) {
	for url := range incomingURLs() {
		start := time.Now()
		value, err := m.Get(url, nil)
		if err != nil {
			log.Print(err)
			continue
		}
		fmt.Printf("%s, %s, %d bytes\n",
			url, time.Since(start), len(value.([]byte)))
	}
}

func Concurrent(t *testing.T, m M) {
	var n sync.WaitGroup
	for url := range incomingURLs() {
		n.Add(1)
		go func(url string) {
			defer n.Done()
			start := time.Now()
			value, err := m.Get(url, nil)
			if err != nil {
				log.Print(err)
				return
			}
			fmt.Printf("%s, %s, %d bytes\n",
				url, time.Since(start), len(value.([]byte)))
		}(url)
	}
	n.Wait()
}

func Cancellation(t *testing.T, m M) {
	tests := []struct {
		url       string
		timeout   time.Duration
		cancelled bool
	}{
		{"https://golang.org", 300 * time.Millisecond, false},
		{"https://godoc.org", 300 * time.Millisecond, false},
		{"https://play.golang.org", 10 * time.Millisecond, true},
		{"http://gopl.io", 300 * time.Millisecond, false},
		{"https://golang.org", 300 * time.Millisecond, false},
		{"https://godoc.org", 300 * time.Millisecond, false},
		{"https://play.golang.org", 300 * time.Millisecond, false},
		{"http://gopl.io", 300 * time.Millisecond, false},
	}

	for i, test := range tests {
		cancel := make(chan struct{})
		start := time.Now()
		go func(cancel chan struct{}) {
			<-time.After(test.timeout)
			close(cancel)
		}(cancel)
		_, err := m.Get(test.url, cancel)
		fmt.Printf("i: %d, %s, %s\n", i, test.url, time.Since(start))
		if err == errCancel && !test.cancelled || test.cancelled && err != errCancel {
			t.Errorf("i:%d, url: '%s', timeout: %v, cancelled: %v",
				i, test.url, test.timeout, test.cancelled)
		}
	}
}

func TestSequential(t *testing.T) {
	m := New(HTTPGetBody)
	defer m.Close()
	Sequential(t, m)
}

func TestConcurrent(t *testing.T) {
	m := New(HTTPGetBody)
	defer m.Close()
	Concurrent(t, m)
}

func TestCancellation(t *testing.T) {
	m := New(HTTPGetBody)
	defer m.Close()
	Cancellation(t, m)
}
<|endoftext|>


<commit_before>package gorush

import (
	"crypto/ecdsa"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"net/http"
	"path/filepath"
	"sync"
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"
	"github.com/sideshow/apns2/payload"
	"github.com/sideshow/apns2/token"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/http2"
)

var idleConnTimeout = 90 * time.Second

// Sound sets the aps sound on the payload.
type Sound struct {
	Critical int     `json:"critical,omitempty"`
	Name     string  `json:"name,omitempty"`
	Volume   float32 `json:"volume,omitempty"`
}

// InitAPNSClient use for initialize APNs Client.
func InitAPNSClient() error {
	if PushConf.Ios.Enabled {
		var err error
		var authKey *ecdsa.PrivateKey
		var certificateKey tls.Certificate
		var ext string

		if PushConf.Ios.KeyPath != "" {
			ext = filepath.Ext(PushConf.Ios.KeyPath)

			switch ext {
			case ".p12":
				certificateKey, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
			case ".pem":
				certificateKey, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password)
			case ".p8":
				authKey, err = token.AuthKeyFromFile(PushConf.Ios.KeyPath)
			default:
				err = errors.New("wrong certificate key extension")
			}

			if err != nil {
				LogError.Error("Cert Error:", err.Error())

				return err
			}
		} else if PushConf.Ios.KeyBase64 != "" {
			ext = "." + PushConf.Ios.KeyType
			key, err := base64.StdEncoding.DecodeString(PushConf.Ios.KeyBase64)
			if err != nil {
				LogError.Error("base64 decode error:", err.Error())

				return err
			}
			switch ext {
			case ".p12":
				certificateKey, err = certificate.FromP12Bytes(key, PushConf.Ios.Password)
			case ".pem":
				certificateKey, err = certificate.FromPemBytes(key, PushConf.Ios.Password)
			case ".p8":
				authKey, err = token.AuthKeyFromBytes(key)
			default:
				err = errors.New("wrong certificate key type")
			}

			if err != nil {
				LogError.Error("Cert Error:", err.Error())

				return err
			}
		}

		if ext == ".p8" && PushConf.Ios.KeyID != "" && PushConf.Ios.TeamID != "" {
			token := &token.Token{
				AuthKey: authKey,
				// KeyID from developer account (Certificates, Identifiers & Profiles -> Keys)
				KeyID: PushConf.Ios.KeyID,
				// TeamID from developer account (View Account -> Membership)
				TeamID: PushConf.Ios.TeamID,
			}

			ApnsClient, err = newApnsTokenClient(token)
		} else {
			ApnsClient, err = newApnsClient(certificateKey)
		}

		if err != nil {
			LogError.Error("Transport Error:", err.Error())

			return err
		}
	}

	return nil
}

func newApnsClient(certificate tls.Certificate) (*apns2.Client, error) {
	var client *apns2.Client

	if PushConf.Ios.Production {
		client = apns2.NewClient(certificate).Production()
	} else {
		client = apns2.NewClient(certificate).Development()
	}

	if PushConf.Core.HTTPProxy == "" {
		return client, nil
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{certificate},
	}

	if len(certificate.Certificate) > 0 {
		tlsConfig.BuildNameToCertificate()
	}

	transport := &http.Transport{
		TLSClientConfig: tlsConfig,
		Proxy:           http.DefaultTransport.(*http.Transport).Proxy,
		IdleConnTimeout: idleConnTimeout,
	}

	transportErr := http2.ConfigureTransport(transport)
	if transportErr != nil {
		return nil, transportErr
	}

	client.HTTPClient.Transport = transport

	return client, nil
}

func newApnsTokenClient(token *token.Token) (*apns2.Client, error) {
	var client *apns2.Client

	if PushConf.Ios.Production {
		client = apns2.NewTokenClient(token).Production()
	} else {
		client = apns2.NewTokenClient(token).Development()
	}

	if PushConf.Core.HTTPProxy == "" {
		return client, nil
	}

	transport := &http.Transport{
		Proxy:           http.DefaultTransport.(*http.Transport).Proxy,
		IdleConnTimeout: idleConnTimeout,
	}

	transportErr := http2.ConfigureTransport(transport)
	if transportErr != nil {
		return nil, transportErr
	}

	client.HTTPClient.Transport = transport

	return client, nil
}

func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload {
	// Alert dictionary

	if len(req.Title) > 0 {
		payload.AlertTitle(req.Title)
	}

	if len(req.Alert.Title) > 0 {
		payload.AlertTitle(req.Alert.Title)
	}

	// Apple Watch & Safari display this string as part of the notification interface.
	if len(req.Alert.Subtitle) > 0 {
		payload.AlertSubtitle(req.Alert.Subtitle)
	}

	if len(req.Alert.TitleLocKey) > 0 {
		payload.AlertTitleLocKey(req.Alert.TitleLocKey)
	}

	if len(req.Alert.LocArgs) > 0 {
		payload.AlertLocArgs(req.Alert.LocArgs)
	}

	if len(req.Alert.TitleLocArgs) > 0 {
		payload.AlertTitleLocArgs(req.Alert.TitleLocArgs)
	}

	if len(req.Alert.Body) > 0 {
		payload.AlertBody(req.Alert.Body)
	}

	if len(req.Alert.LaunchImage) > 0 {
		payload.AlertLaunchImage(req.Alert.LaunchImage)
	}

	if len(req.Alert.LocKey) > 0 {
		payload.AlertLocKey(req.Alert.LocKey)
	}

	if len(req.Alert.Action) > 0 {
		payload.AlertAction(req.Alert.Action)
	}

	if len(req.Alert.ActionLocKey) > 0 {
		payload.AlertActionLocKey(req.Alert.ActionLocKey)
	}

	// General
	if len(req.Category) > 0 {
		payload.Category(req.Category)
	}

	if len(req.Alert.SummaryArg) > 0 {
		payload.AlertSummaryArg(req.Alert.SummaryArg)
	}

	if req.Alert.SummaryArgCount > 0 {
		payload.AlertSummaryArgCount(req.Alert.SummaryArgCount)
	}

	return payload
}

// GetIOSNotification use for define iOS notification.
// The iOS Notification Payload
// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
func GetIOSNotification(req PushNotification) *apns2.Notification {
	notification := &apns2.Notification{
		ApnsID:     req.ApnsID,
		Topic:      req.Topic,
		CollapseID: req.CollapseID,
	}

	if req.Expiration != nil {
		notification.Expiration = time.Unix(*req.Expiration, 0)
	}

	if len(req.Priority) > 0 {
		if req.Priority == "normal" {
			notification.Priority = apns2.PriorityLow
		} else if req.Priority == "high" {
			notification.Priority = apns2.PriorityHigh
		}
	}

	if len(req.PushType) > 0 {
		notification.PushType = apns2.EPushType(req.PushType)
	}

	payload := payload.NewPayload()

	// add alert object if message length > 0
	if len(req.Message) > 0 {
		payload.Alert(req.Message)
	}

	// zero value for clear the badge on the app icon.
	if req.Badge != nil && *req.Badge >= 0 {
		payload.Badge(*req.Badge)
	}

	if req.MutableContent {
		payload.MutableContent()
	}

	switch req.Sound.(type) {
	// from http request binding
	case map[string]interface{}:
		result := &Sound{}
		_ = mapstructure.Decode(req.Sound, &result)
		payload.Sound(result)
	// from http request binding for non critical alerts
	case string:
		payload.Sound(&req.Sound)
	case Sound:
		payload.Sound(&req.Sound)
	}

	if len(req.SoundName) > 0 {
		payload.SoundName(req.SoundName)
	}

	if req.SoundVolume > 0 {
		payload.SoundVolume(req.SoundVolume)
	}

	if req.ContentAvailable {
		payload.ContentAvailable()
	}

	if len(req.URLArgs) > 0 {
		payload.URLArgs(req.URLArgs)
	}

	if len(req.ThreadID) > 0 {
		payload.ThreadID(req.ThreadID)
	}

	for k, v := range req.Data {
		payload.Custom(k, v)
	}

	payload = iosAlertDictionary(payload, req)

	notification.Payload = payload

	return notification
}

func getApnsClient(req PushNotification) (client *apns2.Client) {
	if req.Production {
		client = ApnsClient.Production()
	} else if req.Development {
		client = ApnsClient.Development()
	} else {
		if PushConf.Ios.Production {
			client = ApnsClient.Production()
		} else {
			client = ApnsClient.Development()
		}
	}
	return
}

// PushToIOS provide send notification to APNs server.
func PushToIOS(req PushNotification) bool {
	LogAccess.Debug("Start push notification for iOS")

	var (
		retryCount = 0
		maxRetry   = PushConf.Ios.MaxRetry
	)

	if req.Retry > 0 && req.Retry < maxRetry {
		maxRetry = req.Retry
	}

Retry:
	var (
		isError   = false
		newTokens []string
	)

	notification := GetIOSNotification(req)
	client := getApnsClient(req)

	var wg sync.WaitGroup
	for _, token := range req.Tokens {
		// occupy push slot
		MaxConcurrentIOSPushes <- struct{}{}
		wg.Add(1)
		go func(token string) {
			notification.DeviceToken = token

			// send ios notification
			res, err := client.Push(notification)

			if err != nil || res.StatusCode != 200 {
				if err == nil {
					// error message:
					// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
					err = errors.New(res.Reason)
				}
				// apns server error
				LogPush(FailedPush, token, req, err)

				if PushConf.Core.Sync {
					req.AddLog(getLogPushEntry(FailedPush, token, req, err))
				} else if PushConf.Core.FeedbackURL != "" {
					go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) {
						err := DispatchFeedback(log, url, timeout)
						if err != nil {
							logger.Error(err)
						}
					}(LogError, getLogPushEntry(FailedPush, token, req, err), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout)
				}

				StatStorage.AddIosError(1)
				// We should retry only "retryable" statuses. More info about response:
				// https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns
				if res.StatusCode >= http.StatusInternalServerError {
					newTokens = append(newTokens, token)
				}
				isError = true
			}

			if res.Sent() && !isError {
				LogPush(SucceededPush, token, req, nil)
				StatStorage.AddIosSuccess(1)
			}
			// free push slot
			<-MaxConcurrentIOSPushes
			wg.Done()
		}(token)
	}
	wg.Wait()

	if isError && retryCount < maxRetry {
		retryCount++

		// resend fail token
		req.Tokens = newTokens
		goto Retry
	}

	return isError
}
<commit_msg>ReImplement DialTLS fix from apns2 (#509)<commit_after>package gorush

import (
	"crypto/ecdsa"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"net"
	"net/http"
	"path/filepath"
	"sync"
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"
	"github.com/sideshow/apns2/payload"
	"github.com/sideshow/apns2/token"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/http2"
)

var (
	idleConnTimeout = 90 * time.Second
	tlsDialTimeout  = 20 * time.Second
	tcpKeepAlive    = 60 * time.Second
)

// DialTLS is the default dial function for creating TLS connections for
// non-proxied HTTPS requests.
var DialTLS = func(cfg *tls.Config) func(network, addr string) (net.Conn, error) {
	return func(network, addr string) (net.Conn, error) {
		dialer := &net.Dialer{
			Timeout:   tlsDialTimeout,
			KeepAlive: tcpKeepAlive,
		}
		return tls.DialWithDialer(dialer, network, addr, cfg)
	}
}

// Sound sets the aps sound on the payload.
type Sound struct {
	Critical int     `json:"critical,omitempty"`
	Name     string  `json:"name,omitempty"`
	Volume   float32 `json:"volume,omitempty"`
}

// InitAPNSClient use for initialize APNs Client.
func InitAPNSClient() error {
	if PushConf.Ios.Enabled {
		var err error
		var authKey *ecdsa.PrivateKey
		var certificateKey tls.Certificate
		var ext string

		if PushConf.Ios.KeyPath != "" {
			ext = filepath.Ext(PushConf.Ios.KeyPath)

			switch ext {
			case ".p12":
				certificateKey, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
			case ".pem":
				certificateKey, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password)
			case ".p8":
				authKey, err = token.AuthKeyFromFile(PushConf.Ios.KeyPath)
			default:
				err = errors.New("wrong certificate key extension")
			}

			if err != nil {
				LogError.Error("Cert Error:", err.Error())

				return err
			}
		} else if PushConf.Ios.KeyBase64 != "" {
			ext = "." + PushConf.Ios.KeyType
			key, err := base64.StdEncoding.DecodeString(PushConf.Ios.KeyBase64)
			if err != nil {
				LogError.Error("base64 decode error:", err.Error())

				return err
			}
			switch ext {
			case ".p12":
				certificateKey, err = certificate.FromP12Bytes(key, PushConf.Ios.Password)
			case ".pem":
				certificateKey, err = certificate.FromPemBytes(key, PushConf.Ios.Password)
			case ".p8":
				authKey, err = token.AuthKeyFromBytes(key)
			default:
				err = errors.New("wrong certificate key type")
			}

			if err != nil {
				LogError.Error("Cert Error:", err.Error())

				return err
			}
		}

		if ext == ".p8" && PushConf.Ios.KeyID != "" && PushConf.Ios.TeamID != "" {
			token := &token.Token{
				AuthKey: authKey,
				// KeyID from developer account (Certificates, Identifiers & Profiles -> Keys)
				KeyID: PushConf.Ios.KeyID,
				// TeamID from developer account (View Account -> Membership)
				TeamID: PushConf.Ios.TeamID,
			}

			ApnsClient, err = newApnsTokenClient(token)
		} else {
			ApnsClient, err = newApnsClient(certificateKey)
		}

		if err != nil {
			LogError.Error("Transport Error:", err.Error())

			return err
		}
	}

	return nil
}

func newApnsClient(certificate tls.Certificate) (*apns2.Client, error) {
	var client *apns2.Client

	if PushConf.Ios.Production {
		client = apns2.NewClient(certificate).Production()
	} else {
		client = apns2.NewClient(certificate).Development()
	}

	if PushConf.Core.HTTPProxy == "" {
		return client, nil
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{certificate},
	}

	if len(certificate.Certificate) > 0 {
		tlsConfig.BuildNameToCertificate()
	}

	transport := &http.Transport{
		TLSClientConfig: tlsConfig,
		DialTLS:         DialTLS(tlsConfig),
		Proxy:           http.DefaultTransport.(*http.Transport).Proxy,
		IdleConnTimeout: idleConnTimeout,
	}

	transportErr := http2.ConfigureTransport(transport)
	if transportErr != nil {
		return nil, transportErr
	}

	client.HTTPClient.Transport = transport

	return client, nil
}

func newApnsTokenClient(token *token.Token) (*apns2.Client, error) {
	var client *apns2.Client

	if PushConf.Ios.Production {
		client = apns2.NewTokenClient(token).Production()
	} else {
		client = apns2.NewTokenClient(token).Development()
	}

	if PushConf.Core.HTTPProxy == "" {
		return client, nil
	}

	transport := &http.Transport{
		DialTLS:         DialTLS(nil),
		Proxy:           http.DefaultTransport.(*http.Transport).Proxy,
		IdleConnTimeout: idleConnTimeout,
	}

	transportErr := http2.ConfigureTransport(transport)
	if transportErr != nil {
		return nil, transportErr
	}

	client.HTTPClient.Transport = transport

	return client, nil
}

func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload {
	// Alert dictionary

	if len(req.Title) > 0 {
		payload.AlertTitle(req.Title)
	}

	if len(req.Alert.Title) > 0 {
		payload.AlertTitle(req.Alert.Title)
	}

	// Apple Watch & Safari display this string as part of the notification interface.
	if len(req.Alert.Subtitle) > 0 {
		payload.AlertSubtitle(req.Alert.Subtitle)
	}

	if len(req.Alert.TitleLocKey) > 0 {
		payload.AlertTitleLocKey(req.Alert.TitleLocKey)
	}

	if len(req.Alert.LocArgs) > 0 {
		payload.AlertLocArgs(req.Alert.LocArgs)
	}

	if len(req.Alert.TitleLocArgs) > 0 {
		payload.AlertTitleLocArgs(req.Alert.TitleLocArgs)
	}

	if len(req.Alert.Body) > 0 {
		payload.AlertBody(req.Alert.Body)
	}

	if len(req.Alert.LaunchImage) > 0 {
		payload.AlertLaunchImage(req.Alert.LaunchImage)
	}

	if len(req.Alert.LocKey) > 0 {
		payload.AlertLocKey(req.Alert.LocKey)
	}

	if len(req.Alert.Action) > 0 {
		payload.AlertAction(req.Alert.Action)
	}

	if len(req.Alert.ActionLocKey) > 0 {
		payload.AlertActionLocKey(req.Alert.ActionLocKey)
	}

	// General
	if len(req.Category) > 0 {
		payload.Category(req.Category)
	}

	if len(req.Alert.SummaryArg) > 0 {
		payload.AlertSummaryArg(req.Alert.SummaryArg)
	}

	if req.Alert.SummaryArgCount > 0 {
		payload.AlertSummaryArgCount(req.Alert.SummaryArgCount)
	}

	return payload
}

// GetIOSNotification use for define iOS notification.
// The iOS Notification Payload
// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
func GetIOSNotification(req PushNotification) *apns2.Notification {
	notification := &apns2.Notification{
		ApnsID:     req.ApnsID,
		Topic:      req.Topic,
		CollapseID: req.CollapseID,
	}

	if req.Expiration != nil {
		notification.Expiration = time.Unix(*req.Expiration, 0)
	}

	if len(req.Priority) > 0 {
		if req.Priority == "normal" {
			notification.Priority = apns2.PriorityLow
		} else if req.Priority == "high" {
			notification.Priority = apns2.PriorityHigh
		}
	}

	if len(req.PushType) > 0 {
		notification.PushType = apns2.EPushType(req.PushType)
	}

	payload := payload.NewPayload()

	// add alert object if message length > 0
	if len(req.Message) > 0 {
		payload.Alert(req.Message)
	}

	// zero value for clear the badge on the app icon.
	if req.Badge != nil && *req.Badge >= 0 {
		payload.Badge(*req.Badge)
	}

	if req.MutableContent {
		payload.MutableContent()
	}

	switch req.Sound.(type) {
	// from http request binding
	case map[string]interface{}:
		result := &Sound{}
		_ = mapstructure.Decode(req.Sound, &result)
		payload.Sound(result)
	// from http request binding for non critical alerts
	case string:
		payload.Sound(&req.Sound)
	case Sound:
		payload.Sound(&req.Sound)
	}

	if len(req.SoundName) > 0 {
		payload.SoundName(req.SoundName)
	}

	if req.SoundVolume > 0 {
		payload.SoundVolume(req.SoundVolume)
	}

	if req.ContentAvailable {
		payload.ContentAvailable()
	}

	if len(req.URLArgs) > 0 {
		payload.URLArgs(req.URLArgs)
	}

	if len(req.ThreadID) > 0 {
		payload.ThreadID(req.ThreadID)
	}

	for k, v := range req.Data {
		payload.Custom(k, v)
	}

	payload = iosAlertDictionary(payload, req)

	notification.Payload = payload

	return notification
}

func getApnsClient(req PushNotification) (client *apns2.Client) {
	if req.Production {
		client = ApnsClient.Production()
	} else if req.Development {
		client = ApnsClient.Development()
	} else {
		if PushConf.Ios.Production {
			client = ApnsClient.Production()
		} else {
			client = ApnsClient.Development()
		}
	}
	return
}

// PushToIOS provide send notification to APNs server.
func PushToIOS(req PushNotification) bool {
	LogAccess.Debug("Start push notification for iOS")

	var (
		retryCount = 0
		maxRetry   = PushConf.Ios.MaxRetry
	)

	if req.Retry > 0 && req.Retry < maxRetry {
		maxRetry = req.Retry
	}

Retry:
	var (
		isError   = false
		newTokens []string
	)

	notification := GetIOSNotification(req)
	client := getApnsClient(req)

	var wg sync.WaitGroup
	for _, token := range req.Tokens {
		// occupy push slot
		MaxConcurrentIOSPushes <- struct{}{}
		wg.Add(1)
		go func(token string) {
			notification.DeviceToken = token

			// send ios notification
			res, err := client.Push(notification)

			if err != nil || res.StatusCode != 200 {
				if err == nil {
					// error message:
					// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
					err = errors.New(res.Reason)
				}
				// apns server error
				LogPush(FailedPush, token, req, err)

				if PushConf.Core.Sync {
					req.AddLog(getLogPushEntry(FailedPush, token, req, err))
				} else if PushConf.Core.FeedbackURL != "" {
					go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) {
						err := DispatchFeedback(log, url, timeout)
						if err != nil {
							logger.Error(err)
						}
					}(LogError, getLogPushEntry(FailedPush, token, req, err), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout)
				}

				StatStorage.AddIosError(1)
				// We should retry only "retryable" statuses. More info about response:
				// https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns
				if res.StatusCode >= http.StatusInternalServerError {
					newTokens = append(newTokens, token)
				}
				isError = true
			}

			if res.Sent() && !isError {
				LogPush(SucceededPush, token, req, nil)
				StatStorage.AddIosSuccess(1)
			}
			// free push slot
			<-MaxConcurrentIOSPushes
			wg.Done()
		}(token)
	}
	wg.Wait()

	if isError && retryCount < maxRetry {
		retryCount++

		// resend fail token
		req.Tokens = newTokens
		goto Retry
	}

	return isError
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"errors"
	"io"
	"io/ioutil"
	"math"
	"strings"
	"testing"
	"time"

	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/googlecloudplatform/gcsfuse/mutable"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
)

func TestObjectSyncer(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// fakeObjectCreator
////////////////////////////////////////////////////////////////////////

// An objectCreator that records the arguments it is called with, returning
// canned results.
type fakeObjectCreator struct {
	called bool

	// Supplied arguments
	srcObject *gcs.Object
	contents  []byte

	// Canned results
	o   *gcs.Object
	err error
}

func (oc *fakeObjectCreator) Create(
	ctx context.Context,
	srcObject *gcs.Object,
	r io.Reader) (o *gcs.Object, err error) {
	// Have we been called more than once?
	AssertFalse(oc.called)
	oc.called = true

	// Record args.
	oc.srcObject = srcObject
	oc.contents, err = ioutil.ReadAll(r)
	AssertEq(nil, err)

	// Return results.
	o, err = oc.o, oc.err
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const srcObjectContents = "taco"
const appendThreshold = uint64(len(srcObjectContents))

type ObjectSyncerTest struct {
	ctx context.Context

	fullCreator   fakeObjectCreator
	appendCreator fakeObjectCreator

	bucket gcs.Bucket
	leaser lease.FileLeaser
	syncer ObjectSyncer
	clock  timeutil.SimulatedClock

	srcObject *gcs.Object
	content   mutable.Content
}

var _ SetUpInterface = &ObjectSyncerTest{}

func init() { RegisterTestSuite(&ObjectSyncerTest{}) }

func (t *ObjectSyncerTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Set up dependencies.
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")
	t.leaser = lease.NewFileLeaser("", math.MaxInt32, math.MaxInt32)
	t.syncer = newObjectSyncer(
		appendThreshold,
		&t.fullCreator,
		&t.appendCreator)

	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up a source object.
	t.srcObject, err = t.bucket.CreateObject(
		t.ctx,
		&gcs.CreateObjectRequest{
			Name:     "foo",
			Contents: strings.NewReader(srcObjectContents),
		})

	AssertEq(nil, err)

	// Wrap a mutable.Content around it.
	t.content = mutable.NewContent(
		NewReadProxy(
			t.srcObject,
			nil,            // Initial read lease
			math.MaxUint64, // Chunk size
			t.leaser,
			t.bucket),
		&t.clock)

	// Return errors from the fakes by default.
	t.fullCreator.err = errors.New("Fake error")
	t.appendCreator.err = errors.New("Fake error")
}

func (t *ObjectSyncerTest) call() (
	rl lease.ReadLease, o *gcs.Object, err error) {
	rl, o, err = t.syncer.SyncObject(t.ctx, t.srcObject, t.content)
	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ObjectSyncerTest) NotDirty() {
	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(nil, rl)
	ExpectEq(nil, o)

	// Neither creater should have been called.
	ExpectFalse(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SmallerThanSource() {
	// Truncate downward.
	err := t.content.Truncate(t.ctx, int64(len(srcObjectContents)-1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SameSizeAsSource() {
	// Dirty a byte without changing the length.
	_, err := t.content.WriteAt(
		t.ctx,
		[]byte("a"),
		int64(len(srcObjectContents)-1))

	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) LargerThanSource_ThresholdInSource() {
	var err error

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+100))
	AssertEq(nil, err)

	// But dirty a byte within the initial content.
	_, err = t.content.WriteAt(
		t.ctx,
		[]byte("a"),
		int64(len(srcObjectContents)-1))

	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SourceTooShortForAppend() {
	var err error

	// Recreate the syncer with a higher append threshold.
	t.syncer = newObjectSyncer(
		uint64(len(srcObjectContents)+1),
		&t.fullCreator,
		&t.appendCreator)

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SourceComponentCountTooHigh() {
	var err error

	// Simulate a large component count.
	t.srcObject.ComponentCount = gcs.MaxComponentCount

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) LargerThanSource_ThresholdAtEndOfSource() {
	var err error

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The append creator should be called.
	t.call()

	ExpectFalse(t.fullCreator.called)
	ExpectTrue(t.appendCreator.called)
}

func (t *ObjectSyncerTest) CallsFullCreator() {
	var err error
	AssertLt(2, t.srcObject.Size)

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	t.call()

	AssertTrue(t.fullCreator.called)
	ExpectEq(t.srcObject, t.fullCreator.srcObject)
	ExpectEq(srcObjectContents[:2], string(t.fullCreator.contents))
}

func (t *ObjectSyncerTest) FullCreatorFails() {
	var err error
	t.fullCreator.err = errors.New("taco")

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectThat(err, Error(HasSubstr("fullCreator.Create")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ObjectSyncerTest) FullCreatorReturnsPreconditionError() {
	var err error
	t.fullCreator.err = &gcs.PreconditionError{}

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectEq(t.fullCreator.err, err)
}

func (t *ObjectSyncerTest) FullCreatorSucceeds() {
	var err error
	t.fullCreator.o = &gcs.Object{}
	t.fullCreator.err = nil

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(t.fullCreator.o, o)

	// Check the read lease.
	_, err = rl.Seek(0, 0)
	AssertEq(nil, err)

	buf, err := ioutil.ReadAll(rl)
	AssertEq(nil, err)
	ExpectEq(srcObjectContents[:2], string(buf))
}

func (t *ObjectSyncerTest) AppendCreatorFails() {
	AssertTrue(false, "TODO")
}

func (t *ObjectSyncerTest) AppendCreatorReturnsPreconditionError() {
	AssertTrue(false, "TODO")
}

func (t *ObjectSyncerTest) AppendCreatorSucceeds() {
	var err error
	t.appendCreator.o = &gcs.Object{}
	t.appendCreator.err = nil

	// Append some data.
	_, err = t.content.WriteAt(t.ctx, []byte("burrito"), int64(t.srcObject.Size))
	AssertEq(nil, err)

	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(t.appendCreator.o, o)

	// Check the read lease.
	_, err = rl.Seek(0, 0)
	AssertEq(nil, err)

	buf, err := ioutil.ReadAll(rl)
	AssertEq(nil, err)
	ExpectEq(srcObjectContents+"burrito", string(buf))
}
<commit_msg>ObjectSyncerTest.AppendCreatorReturnsPreconditionError<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"errors"
	"io"
	"io/ioutil"
	"math"
	"strings"
	"testing"
	"time"

	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/googlecloudplatform/gcsfuse/mutable"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
)

func TestObjectSyncer(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// fakeObjectCreator
////////////////////////////////////////////////////////////////////////

// An objectCreator that records the arguments it is called with, returning
// canned results.
type fakeObjectCreator struct {
	called bool

	// Supplied arguments
	srcObject *gcs.Object
	contents  []byte

	// Canned results
	o   *gcs.Object
	err error
}

func (oc *fakeObjectCreator) Create(
	ctx context.Context,
	srcObject *gcs.Object,
	r io.Reader) (o *gcs.Object, err error) {
	// Have we been called more than once?
	AssertFalse(oc.called)
	oc.called = true

	// Record args.
	oc.srcObject = srcObject
	oc.contents, err = ioutil.ReadAll(r)
	AssertEq(nil, err)

	// Return results.
	o, err = oc.o, oc.err
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const srcObjectContents = "taco"
const appendThreshold = uint64(len(srcObjectContents))

type ObjectSyncerTest struct {
	ctx context.Context

	fullCreator   fakeObjectCreator
	appendCreator fakeObjectCreator

	bucket gcs.Bucket
	leaser lease.FileLeaser
	syncer ObjectSyncer
	clock  timeutil.SimulatedClock

	srcObject *gcs.Object
	content   mutable.Content
}

var _ SetUpInterface = &ObjectSyncerTest{}

func init() { RegisterTestSuite(&ObjectSyncerTest{}) }

func (t *ObjectSyncerTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Set up dependencies.
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")
	t.leaser = lease.NewFileLeaser("", math.MaxInt32, math.MaxInt32)
	t.syncer = newObjectSyncer(
		appendThreshold,
		&t.fullCreator,
		&t.appendCreator)

	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up a source object.
	t.srcObject, err = t.bucket.CreateObject(
		t.ctx,
		&gcs.CreateObjectRequest{
			Name:     "foo",
			Contents: strings.NewReader(srcObjectContents),
		})

	AssertEq(nil, err)

	// Wrap a mutable.Content around it.
	t.content = mutable.NewContent(
		NewReadProxy(
			t.srcObject,
			nil,            // Initial read lease
			math.MaxUint64, // Chunk size
			t.leaser,
			t.bucket),
		&t.clock)

	// Return errors from the fakes by default.
	t.fullCreator.err = errors.New("Fake error")
	t.appendCreator.err = errors.New("Fake error")
}

func (t *ObjectSyncerTest) call() (
	rl lease.ReadLease, o *gcs.Object, err error) {
	rl, o, err = t.syncer.SyncObject(t.ctx, t.srcObject, t.content)
	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ObjectSyncerTest) NotDirty() {
	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(nil, rl)
	ExpectEq(nil, o)

	// Neither creater should have been called.
	ExpectFalse(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SmallerThanSource() {
	// Truncate downward.
	err := t.content.Truncate(t.ctx, int64(len(srcObjectContents)-1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SameSizeAsSource() {
	// Dirty a byte without changing the length.
	_, err := t.content.WriteAt(
		t.ctx,
		[]byte("a"),
		int64(len(srcObjectContents)-1))

	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) LargerThanSource_ThresholdInSource() {
	var err error

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+100))
	AssertEq(nil, err)

	// But dirty a byte within the initial content.
	_, err = t.content.WriteAt(
		t.ctx,
		[]byte("a"),
		int64(len(srcObjectContents)-1))

	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SourceTooShortForAppend() {
	var err error

	// Recreate the syncer with a higher append threshold.
	t.syncer = newObjectSyncer(
		uint64(len(srcObjectContents)+1),
		&t.fullCreator,
		&t.appendCreator)

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) SourceComponentCountTooHigh() {
	var err error

	// Simulate a large component count.
	t.srcObject.ComponentCount = gcs.MaxComponentCount

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The full creator should be called.
	t.call()

	ExpectTrue(t.fullCreator.called)
	ExpectFalse(t.appendCreator.called)
}

func (t *ObjectSyncerTest) LargerThanSource_ThresholdAtEndOfSource() {
	var err error

	// Extend the length of the content.
	err = t.content.Truncate(t.ctx, int64(len(srcObjectContents)+1))
	AssertEq(nil, err)

	// The append creator should be called.
	t.call()

	ExpectFalse(t.fullCreator.called)
	ExpectTrue(t.appendCreator.called)
}

func (t *ObjectSyncerTest) CallsFullCreator() {
	var err error
	AssertLt(2, t.srcObject.Size)

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	t.call()

	AssertTrue(t.fullCreator.called)
	ExpectEq(t.srcObject, t.fullCreator.srcObject)
	ExpectEq(srcObjectContents[:2], string(t.fullCreator.contents))
}

func (t *ObjectSyncerTest) FullCreatorFails() {
	var err error
	t.fullCreator.err = errors.New("taco")

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectThat(err, Error(HasSubstr("fullCreator.Create")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ObjectSyncerTest) FullCreatorReturnsPreconditionError() {
	var err error
	t.fullCreator.err = &gcs.PreconditionError{}

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectEq(t.fullCreator.err, err)
}

func (t *ObjectSyncerTest) FullCreatorSucceeds() {
	var err error
	t.fullCreator.o = &gcs.Object{}
	t.fullCreator.err = nil

	// Truncate downward.
	err = t.content.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(t.fullCreator.o, o)

	// Check the read lease.
	_, err = rl.Seek(0, 0)
	AssertEq(nil, err)

	buf, err := ioutil.ReadAll(rl)
	AssertEq(nil, err)
	ExpectEq(srcObjectContents[:2], string(buf))
}

func (t *ObjectSyncerTest) AppendCreatorFails() {
	var err error
	t.appendCreator.err = errors.New("taco")

	// Append some data.
	_, err = t.content.WriteAt(t.ctx, []byte("burrito"), int64(t.srcObject.Size))
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectThat(err, Error(HasSubstr("appendCreator.Create")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ObjectSyncerTest) AppendCreatorReturnsPreconditionError() {
	var err error
	t.appendCreator.err = &gcs.PreconditionError{}

	// Append some data.
	_, err = t.content.WriteAt(t.ctx, []byte("burrito"), int64(t.srcObject.Size))
	AssertEq(nil, err)

	// Call
	_, _, err = t.call()

	ExpectEq(t.appendCreator.err, err)
}

func (t *ObjectSyncerTest) AppendCreatorSucceeds() {
	var err error
	t.appendCreator.o = &gcs.Object{}
	t.appendCreator.err = nil

	// Append some data.
	_, err = t.content.WriteAt(t.ctx, []byte("burrito"), int64(t.srcObject.Size))
	AssertEq(nil, err)

	// Call
	rl, o, err := t.call()

	AssertEq(nil, err)
	ExpectEq(t.appendCreator.o, o)

	// Check the read lease.
	_, err = rl.Seek(0, 0)
	AssertEq(nil, err)

	buf, err := ioutil.ReadAll(rl)
	AssertEq(nil, err)
	ExpectEq(srcObjectContents+"burrito", string(buf))
}
<|endoftext|>


<commit_before>/**
 * Exported named Doozer port.
 * This binds to an anonymous port, exports the host:port pair through Doozer
 * and returns the port to the caller.
 */
package exportedservice

import (
	"crypto/tls"
	"fmt"
	"net"

	"github.com/4ad/doozer"
)

// We need to initialize our Doozer client beforehand and keep it somewhere.
type ServiceExporter struct {
	conn *doozer.Conn
	uri  string
	buri string
}

/**
 * Try to create a new exporter by connecting to Doozer.
 */
func NewExporter(uri, buri string) (*ServiceExporter, error) {
	var self *ServiceExporter = &ServiceExporter{}
	var err error

	self.conn, err = doozer.DialUri(uri, buri)

	// We couldn't connect, let our user know.
	if err != nil {
		return nil, err
	}
	return self, nil
}

/**
 * Open a new anonymous port on "ip" and export it through Doozer as
 * "servicename". If "ip" is a host:port pair, the port will be overridden.
 */
func (self *ServiceExporter) NewExportedPort(
	network, ip, servicename string) (net.Listener, error) {
	var host, hostport string
	var l net.Listener
	var err error
	var i uint

	if host, _, err = net.SplitHostPort(ip); err != nil {
		// Apparently, it's not in host:port format.
		host = ip
	}

	hostport = net.JoinHostPort(host, "0")
	if l, err = net.Listen(network, hostport); err != nil {
		return nil, err
	}

	// Now write our host:port pair to Doozer. First, determine the next
	// free number.
	// FIXME(caoimhe): Turn this into a more efficient implementation.
	for {
		var path string = fmt.Sprintf("/ns/service/%s/%d",
			servicename, i)
		var ok bool
		var derr *doozer.Error
		_, err = self.conn.Set(path, 0, []byte(l.Addr().String()))
		if err == nil {
			return l, nil
		}

		if derr, ok = err.(*doozer.Error); !ok ||
			derr.Err != doozer.ErrOldRev {
			return nil, err
		}

		i += 1
	}
	return nil, err
}

/**
 * Open a new anonymous port on "ip" and export it through Doozer as
 * "servicename". Associate the TLS configuration "config". If "ip" is
 * a host:port pair, the port will be overridden.
 */
func (self *ServiceExporter) NewExportedTLSPort(
	network, ip, servicename string,
	config *tls.Config) (net.Listener, error) {
	var l net.Listener
	var err error

	// We can just create a new port as above...
	l, err = self.NewExportedPort(network, ip, servicename)
	if err != nil {
		return nil, err
	}

	// ... and inject a TLS context.
	return tls.NewListener(l, config), nil
}
<commit_msg>Implement a way to unexport a port, e.g. when the task exits.<commit_after>/**
 * Exported named Doozer port.
 * This binds to an anonymous port, exports the host:port pair through Doozer
 * and returns the port to the caller.
 */
package exportedservice

import (
	"crypto/tls"
	"fmt"
	"net"

	"github.com/4ad/doozer"
)

// We need to initialize our Doozer client beforehand and keep it somewhere.
type ServiceExporter struct {
	conn    *doozer.Conn
	path    string
	pathrev int64
	uri     string
	buri    string
}

/**
 * Try to create a new exporter by connecting to Doozer.
 */
func NewExporter(uri, buri string) (*ServiceExporter, error) {
	var self *ServiceExporter = &ServiceExporter{}
	var err error

	self.conn, err = doozer.DialUri(uri, buri)

	// We couldn't connect, let our user know.
	if err != nil {
		return nil, err
	}
	return self, nil
}

/**
 * Open a new anonymous port on "ip" and export it through Doozer as
 * "servicename". If "ip" is a host:port pair, the port will be overridden.
 */
func (self *ServiceExporter) NewExportedPort(
	network, ip, servicename string) (net.Listener, error) {
	var host, hostport string
	var l net.Listener
	var err error
	var i uint

	if host, _, err = net.SplitHostPort(ip); err != nil {
		// Apparently, it's not in host:port format.
		host = ip
	}

	hostport = net.JoinHostPort(host, "0")
	if l, err = net.Listen(network, hostport); err != nil {
		return nil, err
	}

	// Now write our host:port pair to Doozer. First, determine the next
	// free number.
	// FIXME(caoimhe): Turn this into a more efficient implementation.
	for {
		var path string = fmt.Sprintf("/ns/service/%s/%d", servicename, i)
		var ok bool
		var derr *doozer.Error
		var rev int64

		rev, err = self.conn.Set(path, 0, []byte(l.Addr().String()))
		if err == nil {
			self.path = path
			self.pathrev = rev
			return l, nil
		}

		if derr, ok = err.(*doozer.Error); !ok ||
			derr.Err != doozer.ErrOldRev {
			return nil, err
		}

		i += 1
	}
	return nil, err
}

/**
 * Open a new anonymous port on "ip" and export it through Doozer as
 * "servicename". Associate the TLS configuration "config". If "ip" is
 * a host:port pair, the port will be overridden.
 */
func (self *ServiceExporter) NewExportedTLSPort(
	network, ip, servicename string,
	config *tls.Config) (net.Listener, error) {
	var l net.Listener
	var err error

	// We can just create a new port as above...
	l, err = self.NewExportedPort(network, ip, servicename)
	if err != nil {
		return nil, err
	}

	// ... and inject a TLS context.
	return tls.NewListener(l, config), nil
}

/**
 * Remove the associated exported port. This will only delete the most
 * recently exported port.
 */
func (self *ServiceExporter) UnexportPort() error {
	var derr *doozer.Error
	var err error
	var ok bool

	if err = self.conn.Del(self.path, self.pathrev); err != nil {
		if derr, ok = err.(*doozer.Error); !ok ||
			derr.Err != doozer.ErrOldRev {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package neoutil

import (
	"github.com/jmcvetta/neoism"
)

func EnsureIndex(db *neoism.Database, label string, prop string) error {
	indexes, err := db.Indexes(label)
	if err != nil {
		return err
	}
	for _, ind := range indexes {
		if len(ind.PropertyKeys) == 1 && ind.PropertyKeys[0] == prop {
			return nil
		}
	}
	if _, err := db.CreateIndex(label, prop); err != nil {
		return err
	}
	return nil
}
<commit_msg>ensure many at once.<commit_after>package neoutil

import (
	"github.com/jmcvetta/neoism"
)

func EnsureIndex(db *neoism.Database, label string, prop string) error {
	indexes, err := db.Indexes(label)
	if err != nil {
		return err
	}
	for _, ind := range indexes {
		if len(ind.PropertyKeys) == 1 && ind.PropertyKeys[0] == prop {
			return nil
		}
	}
	if _, err := db.CreateIndex(label, prop); err != nil {
		return err
	}
	return nil
}

func EnsureIndexes(db *neoism.Database, labelToProperty map[string]string) error {
	for lab, prop := range labelToProperty {
		if err := EnsureIndex(db, lab, prop); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package fingerprint

import (
	"fmt"
	"log"
	"strconv"
	"time"

	consul "github.com/hashicorp/consul/api"

	client "github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/nomad/structs"
)

// ConsulFingerprint is used to fingerprint the architecture
type ConsulFingerprint struct {
	logger *log.Logger
}

// NewConsulFingerprint is used to create an OS fingerprint
func NewConsulFingerprint(logger *log.Logger) Fingerprint {
	f := &ConsulFingerprint{logger: logger}
	return f
}

func (f *ConsulFingerprint) Fingerprint(config *client.Config, node *structs.Node) (bool, error) {
	// Guard against uninitialized Links
	if node.Links == nil {
		node.Links = map[string]string{}
	}

	address := config.ReadDefault("consul.address", "127.0.0.1:8500")
	timeout, err := time.ParseDuration(config.ReadDefault("consul.timeout", "10ms"))
	if err != nil {
		return false, fmt.Errorf("Unable to parse consul.timeout: %s", err)
	}

	consulConfig := consul.DefaultConfig()
	consulConfig.Address = address
	consulConfig.HttpClient.Timeout = timeout

	consulClient, err := consul.NewClient(consulConfig)
	if err != nil {
		return false, fmt.Errorf("Failed to initialize consul client: %s", err)
	}

	// We'll try to detect consul by making a query to to the agent's self API.
	// If we can't hit this URL consul is probably not running on this machine.
	info, err := consulClient.Agent().Self()
	if err != nil {
		return false, nil
	}

	node.Attributes["consul.server"] = strconv.FormatBool(info["Config"]["Server"].(bool))
	node.Attributes["consul.version"] = info["Config"]["Version"].(string)
	node.Attributes["consul.revision"] = info["Config"]["Revision"].(string)
	node.Attributes["consul.name"] = info["Config"]["NodeName"].(string)
	node.Attributes["consul.datacenter"] = info["Config"]["Datacenter"].(string)

	node.Links["consul"] = fmt.Sprintf("%s.%s",
		node.Attributes["consul.datacenter"],
		node.Attributes["consul.name"])

	return true, nil
}

func (f *ConsulFingerprint) Periodic() (bool, time.Duration) {
	return true, 15 * time.Second
}
<commit_msg>Fingerprinter only creates one consul client<commit_after>package fingerprint

import (
	"fmt"
	"log"
	"strconv"
	"time"

	consul "github.com/hashicorp/consul/api"

	client "github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/nomad/structs"
)

// ConsulFingerprint is used to fingerprint the architecture
type ConsulFingerprint struct {
	logger *log.Logger
	client *consul.Client
}

// NewConsulFingerprint is used to create an OS fingerprint
func NewConsulFingerprint(logger *log.Logger) Fingerprint {
	f := &ConsulFingerprint{logger: logger}
	return f
}

func (f *ConsulFingerprint) Fingerprint(config *client.Config, node *structs.Node) (bool, error) {
	// Guard against uninitialized Links
	if node.Links == nil {
		node.Links = map[string]string{}
	}

	// Only create the client once to avoid creating too many connections to
	// Consul.
	if f.client == nil {
		address := config.ReadDefault("consul.address", "127.0.0.1:8500")
		timeout, err := time.ParseDuration(config.ReadDefault("consul.timeout", "10ms"))
		if err != nil {
			return false, fmt.Errorf("Unable to parse consul.timeout: %s", err)
		}

		consulConfig := consul.DefaultConfig()
		consulConfig.Address = address
		consulConfig.HttpClient.Timeout = timeout

		f.client, err = consul.NewClient(consulConfig)
		if err != nil {
			return false, fmt.Errorf("Failed to initialize consul client: %s", err)
		}
	}

	// We'll try to detect consul by making a query to to the agent's self API.
	// If we can't hit this URL consul is probably not running on this machine.
	info, err := f.client.Agent().Self()
	if err != nil {
		return false, nil
	}

	node.Attributes["consul.server"] = strconv.FormatBool(info["Config"]["Server"].(bool))
	node.Attributes["consul.version"] = info["Config"]["Version"].(string)
	node.Attributes["consul.revision"] = info["Config"]["Revision"].(string)
	node.Attributes["consul.name"] = info["Config"]["NodeName"].(string)
	node.Attributes["consul.datacenter"] = info["Config"]["Datacenter"].(string)

	node.Links["consul"] = fmt.Sprintf("%s.%s",
		node.Attributes["consul.datacenter"],
		node.Attributes["consul.name"])

	return true, nil
}

func (f *ConsulFingerprint) Periodic() (bool, time.Duration) {
	return true, 15 * time.Second
}
<|endoftext|>


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

package main

import (
	"fmt"
	"os"

	"launchpad.net/loggo"

	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/juju"
	_ "launchpad.net/juju-core/provider/all"
)

var logger = loggo.GetLogger("juju.plugins.updatebootstrap")

const updateBootstrapDoc = `
Patches all machines after state server has been restored from backup.
`

type updateBootstrapCommand struct {
	cmd.EnvCommandBase
}

func (c *updateBootstrapCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "juju-update-bootstrap",
		Purpose: "update all machines after recovering state server",
		Doc:     updateBootstrapDoc,
	}
}

func (c *updateBootstrapCommand) Run(ctx *cmd.Context) error {
	logger.Infof("Running update-bootstrap")
	return nil
}

func Main(args []string) {
	if err := juju.InitJujuHome(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		os.Exit(2)
	}
	command := updateBootstrapCommand{}
	os.Exit(cmd.Main(&command, cmd.DefaultContext(), args[1:]))
}

func main() {
	Main(os.Args)
}
<commit_msg>Add logic for updating state address in juju-update-bootstrap<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	"launchpad.net/loggo"

	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/juju"
	_ "launchpad.net/juju-core/provider/all"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/utils"
)

var logger = loggo.GetLogger("juju.plugins.updatebootstrap")

const updateBootstrapDoc = `
Patches all machines after state server has been restored from backup, to
update state server address to new location.
`

type updateBootstrapCommand struct {
	cmd.EnvCommandBase
}

func (c *updateBootstrapCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "juju-update-bootstrap",
		Purpose: "update all machines after recovering state server",
		Doc:     updateBootstrapDoc,
	}
}

func (c *updateBootstrapCommand) Run(ctx *cmd.Context) error {
	conn, err := juju.NewConnFromName(c.EnvName)
	if err != nil {
		return err
	}
	defer conn.Close()
	stateAddr, err := GetStateAddress(conn.Environ)
	if err != nil {
		return err
	}
	fmt.Printf("using state address %v\n", stateAddr)
	return updateAllMachines(conn, stateAddr)
}

// GetStateAddress returns the address of one state server
func GetStateAddress(environ environs.Environ) (string, error) {
	// XXX: Can easily look up state server address using api instead
	stateInfo, _, err := environ.StateInfo()
	if err != nil {
		return "", err
	}
	return strings.Split(stateInfo.Addrs[0], ":")[0], nil
}

var agentAddressTemplate = `
set -ex
cd /var/lib/juju/agents
for agent in *; do
	initctl stop jujud-$agent
	sed -i.old -r "/^(stateaddresses|apiaddresses):/{
		n
		s/- .*(:[0-9]+)/- $ADDR\1/
	}" $agent/agent.conf
	initctl start jujud-$agent
done
sed -i -r 's/^(:syslogtag, startswith, "juju-" @)(.*)(:[0-9]+)$/\1$ADDR\3/' /etc/rsyslog.d/*-juju*.conf
`

// renderScriptArg generates an ssh script argument to update state addresses
func renderScriptArg(stateAddr string) string {
	script := strings.Replace(agentAddressTemplate, "$ADDR", stateAddr, -1)
	return "sudo sh -c " + utils.ShQuote(script)
}

// runMachineUpdate connects via ssh to the machine and runs the update script
func runMachineUpdate(m *state.Machine, sshArg string) error {
	addr := instance.SelectPublicAddress(m.Addresses())
	if addr == "" {
		return fmt.Errorf("no appropriate public address found")
	}
	args := []string{
		"-l", "ubuntu",
		"-T",
		"-o", "StrictHostKeyChecking no",
		"-o", "PasswordAuthentication no",
		addr,
		sshArg,
	}
	c := exec.Command("ssh", args...)
	if data, err := c.CombinedOutput(); err != nil {
		return fmt.Errorf("ssh command failed: %v (%q)", err, data)
	}
	return nil
}

// updateAllMachines finds all machines resets the stored state address
func updateAllMachines(conn *juju.Conn, stateAddr string) error {
	// XXX(gz): includes the state server(s) which don't want updating
	machines, err := conn.State.AllMachines()
	if err != nil {
		return err
	}
	for _, machine := range machines {
		logger.Infof("updating machine: %v\n", machine)
		runMachineUpdate(machine, renderScriptArg(stateAddr))
	}
	return nil
}

func Main(args []string) {
	if err := juju.InitJujuHome(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		os.Exit(2)
	}
	command := updateBootstrapCommand{}
	os.Exit(cmd.Main(&command, cmd.DefaultContext(), args[1:]))
}

func main() {
	Main(os.Args)
}
<|endoftext|>


<commit_before>/*
 * Copyright 2019 The Kythe Authors. All rights reserved.
 *
 * 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.
 */

// Binary java_wrapper wraps the real java command to invoke the standalone java extractor
// in parallel with the genuine compilation command.
// As it interjects itself between make and a real java command, all options are
// provided via environment variables.  See the corresponding consts and java_extractor for details.
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"bitbucket.org/creachadair/shell"
	"golang.org/x/sys/unix"
)

const (
	javaCommandVar  = "KYTHE_JAVA_COMMAND"       // Path to the real java command, required.
	extractorJarVar = "KYTHE_JAVA_EXTRACTOR_JAR" // Path to the javac_extractor jar, required.
	kytheOutputVar  = "KYTHE_OUTPUT_DIRECTORY"   // Extraction output directory, required.
	kytheTargetVar  = "KYTHE_ANALYSIS_TARGET"    // Extraction analysis target to set, defaults to the java module name.
)

var (
	modulePattern = regexp.MustCompile("^@/.*/_the.(.*)_batch.tmp$")
)

func moduleName() string {
	// Hackish way to determine the likely module being compiled.
	return modulePattern.ReplaceAllString(os.Args[len(os.Args)-1], "$1")
}

func outputDir() string {
	if val := os.Getenv(kytheOutputVar); val != "" {
		return val
	}
	log.Fatal("ERROR: KYTHE_OUTPUT_DIRECTORY not set")
	return ""
}

func mustGetEnvPath(key string) string {
	if val := os.Getenv(key); val != "" {
		if _, err := os.Stat(val); err != nil {
			log.Fatalf("invalid %s: %v", key, err)
		}
		return val
	}
	log.Fatal(key + " not set")
	return ""
}

func javaCommand() string {
	return mustGetEnvPath(javaCommandVar)
}

func extractorJar() string {
	return mustGetEnvPath(extractorJarVar)
}

func extractorArgs(args []string, jar string) []string {
	isJavac := false
	var result []string
	for len(args) > 0 {
		var a string
		var v string
		switch a, args = shift(args); a {
		case "-m", "--module":
			v, args = shift(args)
			if !strings.HasSuffix(v, ".javac.Main") {
				isJavac = false
				break
			}
			isJavac = true
			result = append(result,
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.main=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.util=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.file=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.api=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.code=ALL-UNNAMED",
				"-jar", jar, "-Xprefer:source")
		case "--add-modules", "--limit-modules":
			v, args = shift(args)
			result = append(result, a, v+",java.logging,java.sql")
		case "--doclint-format":
			_, args = shift(args)
		case "-Werror":
		default:
			switch {
			case strings.HasPrefix(a, "-Xplugin:depend"), strings.HasPrefix(a, "-Xlint:"), strings.HasPrefix(a, "-Xdoclint"):
			case strings.HasPrefix(a, "-Xmx"):
				result = append(result, "-Xmx3G")
			default:
				result = append(result, a)
			}
		}
	}
	// As we can only do anything meaningful with Java compilations,
	// but wrap the java binary, don't attempt to exact other invocations.
	if !isJavac {
		return nil
	}
	return result
}

func extractorEnv() []string {
	env := os.Environ()
	env = setEnvDefault(env, kytheTargetVar, moduleName())
	return env
}

func setEnvDefault(env []string, key, def string) []string {
	if val := os.Getenv(key); val == "" {
		env = append(env, key+"="+def)
	}
	return env
}

func shift(args []string) (string, []string) {
	if len(args) > 0 {
		return args[0], args[1:]
	}
	return "", nil
}

func main() {
	java := javaCommand()
	jar := extractorJar()
	if args := extractorArgs(os.Args[1:], jar); len(args) > 0 {
		cmd := exec.Command(java, args...)
		cmd.Env = extractorEnv()
		log.Printf("*** Extracting: %s", moduleName())
		if output, err := cmd.CombinedOutput(); err != nil {
			w, err := os.Create(filepath.Join(outputDir(), moduleName()+".err"))
			if err != nil {
				log.Fatalf("Error creating error log for module %s: %v", moduleName(), err)
			}
			fmt.Fprintf(w, "--- %s\n", shell.Join(args))
			w.Write(output)
			w.Close()

			// Log, but don't abort, on extraction failures.
			log.Printf("ERROR: extractor failure for module %s: %v", moduleName(), err)
		}
	}
	// Always end by running the java command directly, as "java".
	os.Args[0] = "java"
	log.Fatal(unix.Exec(java, os.Args, os.Environ())) // If exec returns at all, it's an error.
}
<commit_msg>fix(extraction): make java_wrapper handle a few more modules (#3827)<commit_after>/*
 * Copyright 2019 The Kythe Authors. All rights reserved.
 *
 * 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.
 */

// Binary java_wrapper wraps the real java command to invoke the standalone java extractor
// in parallel with the genuine compilation command.
// As it interjects itself between make and a real java command, all options are
// provided via environment variables.  See the corresponding consts and java_extractor for details.
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"bitbucket.org/creachadair/shell"
	"golang.org/x/sys/unix"
)

const (
	javaCommandVar  = "KYTHE_JAVA_COMMAND"       // Path to the real java command, required.
	extractorJarVar = "KYTHE_JAVA_EXTRACTOR_JAR" // Path to the javac_extractor jar, required.
	kytheOutputVar  = "KYTHE_OUTPUT_DIRECTORY"   // Extraction output directory, required.
	kytheTargetVar  = "KYTHE_ANALYSIS_TARGET"    // Extraction analysis target to set, defaults to the java module name.
)

var (
	modulePattern = regexp.MustCompile("^@/.*/_the.(.*)_batch.tmp$")
)

func moduleName() string {
	path := os.Args[len(os.Args)-1]
	// Hackish way to determine the likely module being compiled.
	repl := modulePattern.ReplaceAllString(path, "$1")
	if repl == path { // No match, use dirname and basename to form the module.
		return filepath.Base(filepath.Dir(path)) + "#" + filepath.Base(path)
	}
	return repl
}

func outputDir() string {
	if val := os.Getenv(kytheOutputVar); val != "" {
		return val
	}
	log.Fatal("ERROR: KYTHE_OUTPUT_DIRECTORY not set")
	return ""
}

func mustGetEnvPath(key string) string {
	if val := os.Getenv(key); val != "" {
		if _, err := os.Stat(val); err != nil {
			log.Fatalf("invalid %s: %v", key, err)
		}
		return val
	}
	log.Fatal(key + " not set")
	return ""
}

func javaCommand() string {
	return mustGetEnvPath(javaCommandVar)
}

func extractorJar() string {
	return mustGetEnvPath(extractorJarVar)
}

func extractorArgs(args []string, jar string) []string {
	isJavac := false
	var result []string
	for len(args) > 0 {
		var a string
		var v string
		switch a, args = shift(args); a {
		case "-m", "--module":
			v, args = shift(args)
			if !strings.HasSuffix(v, ".javac.Main") {
				isJavac = false
				break
			}
			isJavac = true
			result = append(result,
				"--add-modules", "java.logging,java.sql",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.main=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.util=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.file=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.api=ALL-UNNAMED",
				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.code=ALL-UNNAMED",
				"-jar", jar, "-Xprefer:source")
		case "--doclint-format":
			_, args = shift(args)
		case "-Werror":
		default:
			switch {
			case strings.HasPrefix(a, "-Xplugin:depend"), strings.HasPrefix(a, "-Xlint:"), strings.HasPrefix(a, "-Xdoclint"):
			case strings.HasPrefix(a, "-Xmx"):
				result = append(result, "-Xmx3G")
			default:
				result = append(result, a)
			}
		}
	}
	// As we can only do anything meaningful with Java compilations,
	// but wrap the java binary, don't attempt to extract other invocations.
	if !isJavac {
		return nil
	}
	return result
}

func extractorEnv() []string {
	env := os.Environ()
	env = setEnvDefault(env, kytheTargetVar, moduleName())
	return env
}

func setEnvDefault(env []string, key, def string) []string {
	if val := os.Getenv(key); val == "" {
		env = append(env, key+"="+def)
	}
	return env
}

func shift(args []string) (string, []string) {
	if len(args) > 0 {
		return args[0], args[1:]
	}
	return "", nil
}

func main() {
	java := javaCommand()
	jar := extractorJar()
	if args := extractorArgs(os.Args[1:], jar); len(args) > 0 {
		cmd := exec.Command(java, args...)
		cmd.Env = extractorEnv()
		log.Printf("*** Extracting: %s", moduleName())
		if output, err := cmd.CombinedOutput(); err != nil {
			w, err := os.Create(filepath.Join(outputDir(), moduleName()+".err"))
			if err != nil {
				log.Fatalf("Error creating error log for module %s: %v", moduleName(), err)
			}
			fmt.Fprintf(w, "--- %s\n", shell.Join(args))
			w.Write(output)
			w.Close()

			// Log, but don't abort, on extraction failures.
			log.Printf("ERROR: extractor failure for module %s: %v", moduleName(), err)
		}
	}
	// Always end by running the java command directly, as "java".
	os.Args[0] = "java"
	log.Fatal(unix.Exec(java, os.Args, os.Environ())) // If exec returns at all, it's an error.
}
<|endoftext|>