<commit_before>package main

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var (
	// countFile is the path to a file (relative to the $LFSTEST_DIR) who's
	// contents is the number of actively-running integration tests.
	countFile = "test_count"
	// lockFile is the path to a file (relative to the $LFSTEST_DIR) who's
	// presence indicates that another invocation of the lfstest-count-tests
	// program is modifying the test_count.
	lockFile = "test_count.lock"

	// lockAcquireTimeout is the maximum amount of time that we will wait
	// for lockFile to become available (and thus the amount of time that we
	// will wait in order to acquire the lock).
	lockAcquireTimeout = 5 * time.Second

	// errCouldNotAcquire indicates that the program could not acquire the
	// lock needed to modify the test_count. It is a fatal error.
	errCouldNotAcquire = fmt.Errorf("could not acquire lock, dying")
)

// countFn is a type signature that all functions who wish to modify the
// test_count must inhabit.
//
// The first and only formal parameter is the current number of running tests
// found in test_count after acquiring the lock.
//
// The returned tuple indicates (1) the new number that should be written to
// test_count, and (2) if there was an error in computing that value. If err is
// non-nil, the program will exit and test_count will not be updated.
type countFn func(int) (int, error)

func main() {
	if len(os.Args) > 2 {
		fmt.Fprintf(os.Stderr,
			"usage: %s [increment|decrement]\n", os.Args[0])
		os.Exit(1)
	}

	ctx, cancel := context.WithTimeout(
		context.Background(), lockAcquireTimeout)
	defer cancel()

	if err := acquire(ctx); err != nil {
		fatal(err)
	}
	defer release()

	if len(os.Args) == 1 {
		// Calling with no arguments indicates that we simply want to
		// read the contents of test_count.
		callWithCount(func(n int) (int, error) {
			fmt.Fprintf(os.Stdout, "%d\n", n)
			return n, nil
		})
		return
	}

	var err error

	switch strings.ToLower(os.Args[1]) {
	case "increment":
		err = callWithCount(func(n int) (int, error) {
			if n > 0 {
				// If n>1, it is therefore true that a
				// lfstest-gitserver invocation is already
				// running.
				//
				// Hence, let's do nothing here other than
				// increase the count.
				return n + 1, nil
			}

			// The lfstest-gitserver invocation (see: below) does
			// not itself create a gitserver.log in the appropriate
			// directory. Thus, let's create it ourselves instead.
			log, err := os.Create(fmt.Sprintf(
				"%s/gitserver.log", os.Getenv("LFSTEST_DIR")))
			if err != nil {
				return n, err
			}

			// The executable name depends on the X environment
			// variable, which is set in script/cibuild.
			var cmd *exec.Cmd
			if runtime.GOOS == "windows" {
				cmd = exec.Command("lfstest-gitserver.exe")
			} else {
				cmd = exec.Command("lfstest-gitserver")
			}

			// The following are ported from the old
			// test/testhelpers.sh, and comprise the requisite
			// environment variables needed to run
			// lfstest-gitserver.
			cmd.Env = append(os.Environ(),
				fmt.Sprintf("LFSTEST_URL=%s", os.Getenv("LFSTEST_URL")),
				fmt.Sprintf("LFSTEST_SSL_URL=%s", os.Getenv("LFSTEST_SSL_URL")),
				fmt.Sprintf("LFSTEST_CLIENT_CERT_URL=%s", os.Getenv("LFSTEST_CLIENT_CERT_URL")),
				fmt.Sprintf("LFSTEST_DIR=%s", os.Getenv("LFSTEST_DIR")),
				fmt.Sprintf("LFSTEST_CERT=%s", os.Getenv("LFSTEST_CERT")),
				fmt.Sprintf("LFSTEST_CLIENT_CERT=%s", os.Getenv("LFSTEST_CLIENT_CERT")),
				fmt.Sprintf("LFSTEST_CLIENT_KEY=%s", os.Getenv("LFSTEST_CLIENT_KEY")),
			)
			cmd.Stdout = log

			// Start performs a fork/execve, hence we can abandon
			// this process once it has started.
			if err := cmd.Start(); err != nil {
				return n, err
			}
			return n + 1, nil
		})
	case "decrement":
		err = callWithCount(func(n int) (int, error) {
			if n > 1 {
				// If there is at least two tests running, we
				// need not shutdown a lfstest-gitserver
				// instance.
				return n - 1, nil
			}

			// Otherwise, we need to POST to /shutdown, which will
			// cause the lfstest-gitserver to abort itself.
			url, err := ioutil.ReadFile(os.Getenv("LFS_URL_FILE"))
			if err == nil {
				_, err = http.Post(string(url)+"/shutdown",
					"application/text",
					strings.NewReader(time.Now().String()))
			}

			return 0, nil
		})
	}

	if err != nil {
		fatal(err)
	}
}

var (
	// acquireTick is the constant time that one tick (i.e., one attempt at
	// acquiring the lock) should last.
	acquireTick = 10 * time.Millisecond
)

// acquire acquires the lock file necessary to perform updates to test_count,
// and returns an error if that lock cannot be acquired.
func acquire(ctx context.Context) error {
	path, err := path(lockFile)
	if err != nil {
		return err
	}

	tick := time.NewTicker(acquireTick)
	defer tick.Stop()

	for {
		select {
		case <-tick.C:
			// Try every tick of the above ticker before giving up
			// and trying again.
			_, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0666)
			if err == nil || !os.IsExist(err) {
				return err
			}
		case <-ctx.Done():
			// If the context.Context above has reached its
			// deadline, we must give up.
			return errCouldNotAcquire
		}
	}
}

// release releases the lock file so that another process can take over, or
// returns an error.
func release() error {
	path, err := path(lockFile)
	if err != nil {
		return err
	}
	return os.Remove(path)
}

// callWithCount calls the given countFn with the current count in test_count,
// and updates it with what the function returns.
//
// If the function produced an error, that will be returned instead.
func callWithCount(fn countFn) error {
	path, err := path(countFile)
	if err != nil {
		return err
	}

	f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		return err
	}

	contents, err := ioutil.ReadAll(f)
	if err != nil {
		return err
	}

	var n int = 0
	if len(contents) != 0 {
		n, err = strconv.Atoi(string(contents))
		if err != nil {
			return err
		}
	}

	after, err := fn(n)
	if err != nil {
		return err
	}

	// We want to write over the contents in the file, so "truncate" the
	// file to a length of 0, and then seek to the beginning of the file to
	// update the write head.
	if err := f.Truncate(0); err != nil {
		return err
	}
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		return err
	}

	if _, err := fmt.Fprintf(f, "%d", after); err != nil {
		return err
	}
	return nil
}

// path returns an absolute path corresponding to any given path relative to the
// 't' directory of the current checkout of Git LFS.
func path(s string) (string, error) {
	p := filepath.Join(filepath.Dir(os.Getenv("LFSTEST_DIR")), s)
	if err := os.MkdirAll(filepath.Dir(p), 0666); err != nil {
		return "", err
	}
	return p, nil
}

// fatal reports the given error (if non-nil), and then dies. If the error was
// nil, nothing happens.
func fatal(err error) {
	if err == nil {
		return
	}
	if err := release(); err != nil {
		fmt.Fprintf(os.Stderr, "fatal: while dying, got: %s\n", err)
	}
	fmt.Fprintf(os.Stderr, "fatal: %s\n", err)
	os.Exit(1)
}
<commit_msg>t/cmd/lfstest-count-tests.go: prefer consistent constant usage<commit_after>package main

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var (
	// countFile is the path to a file (relative to the $LFSTEST_DIR) who's
	// contents is the number of actively-running integration tests.
	countFile = "test_count"
	// lockFile is the path to a file (relative to the $LFSTEST_DIR) who's
	// presence indicates that another invocation of the lfstest-count-tests
	// program is modifying the test_count.
	lockFile = "test_count.lock"

	// lockAcquireTimeout is the maximum amount of time that we will wait
	// for lockFile to become available (and thus the amount of time that we
	// will wait in order to acquire the lock).
	lockAcquireTimeout = 5 * time.Second

	// errCouldNotAcquire indicates that the program could not acquire the
	// lock needed to modify the test_count. It is a fatal error.
	errCouldNotAcquire = fmt.Errorf("could not acquire lock, dying")
)

// countFn is a type signature that all functions who wish to modify the
// test_count must inhabit.
//
// The first and only formal parameter is the current number of running tests
// found in test_count after acquiring the lock.
//
// The returned tuple indicates (1) the new number that should be written to
// test_count, and (2) if there was an error in computing that value. If err is
// non-nil, the program will exit and test_count will not be updated.
type countFn func(int) (int, error)

func main() {
	if len(os.Args) > 2 {
		fmt.Fprintf(os.Stderr,
			"usage: %s [increment|decrement]\n", os.Args[0])
		os.Exit(1)
	}

	ctx, cancel := context.WithTimeout(
		context.Background(), lockAcquireTimeout)
	defer cancel()

	if err := acquire(ctx); err != nil {
		fatal(err)
	}
	defer release()

	if len(os.Args) == 1 {
		// Calling with no arguments indicates that we simply want to
		// read the contents of test_count.
		callWithCount(func(n int) (int, error) {
			fmt.Fprintf(os.Stdout, "%d\n", n)
			return n, nil
		})
		return
	}

	var err error

	switch strings.ToLower(os.Args[1]) {
	case "increment":
		err = callWithCount(func(n int) (int, error) {
			if n > 0 {
				// If n>1, it is therefore true that a
				// lfstest-gitserver invocation is already
				// running.
				//
				// Hence, let's do nothing here other than
				// increase the count.
				return n + 1, nil
			}

			// The lfstest-gitserver invocation (see: below) does
			// not itself create a gitserver.log in the appropriate
			// directory. Thus, let's create it ourselves instead.
			log, err := os.Create(fmt.Sprintf(
				"%s/gitserver.log", os.Getenv("LFSTEST_DIR")))
			if err != nil {
				return n, err
			}

			// The executable name depends on the X environment
			// variable, which is set in script/cibuild.
			var cmd *exec.Cmd
			if runtime.GOOS == "windows" {
				cmd = exec.Command("lfstest-gitserver.exe")
			} else {
				cmd = exec.Command("lfstest-gitserver")
			}

			// The following are ported from the old
			// test/testhelpers.sh, and comprise the requisite
			// environment variables needed to run
			// lfstest-gitserver.
			cmd.Env = append(os.Environ(),
				fmt.Sprintf("LFSTEST_URL=%s", os.Getenv("LFSTEST_URL")),
				fmt.Sprintf("LFSTEST_SSL_URL=%s", os.Getenv("LFSTEST_SSL_URL")),
				fmt.Sprintf("LFSTEST_CLIENT_CERT_URL=%s", os.Getenv("LFSTEST_CLIENT_CERT_URL")),
				fmt.Sprintf("LFSTEST_DIR=%s", os.Getenv("LFSTEST_DIR")),
				fmt.Sprintf("LFSTEST_CERT=%s", os.Getenv("LFSTEST_CERT")),
				fmt.Sprintf("LFSTEST_CLIENT_CERT=%s", os.Getenv("LFSTEST_CLIENT_CERT")),
				fmt.Sprintf("LFSTEST_CLIENT_KEY=%s", os.Getenv("LFSTEST_CLIENT_KEY")),
			)
			cmd.Stdout = log

			// Start performs a fork/execve, hence we can abandon
			// this process once it has started.
			if err := cmd.Start(); err != nil {
				return n, err
			}
			return 1, nil
		})
	case "decrement":
		err = callWithCount(func(n int) (int, error) {
			if n > 1 {
				// If there is at least two tests running, we
				// need not shutdown a lfstest-gitserver
				// instance.
				return n - 1, nil
			}

			// Otherwise, we need to POST to /shutdown, which will
			// cause the lfstest-gitserver to abort itself.
			url, err := ioutil.ReadFile(os.Getenv("LFS_URL_FILE"))
			if err == nil {
				_, err = http.Post(string(url)+"/shutdown",
					"application/text",
					strings.NewReader(time.Now().String()))
			}

			return 0, nil
		})
	}

	if err != nil {
		fatal(err)
	}
}

var (
	// acquireTick is the constant time that one tick (i.e., one attempt at
	// acquiring the lock) should last.
	acquireTick = 10 * time.Millisecond
)

// acquire acquires the lock file necessary to perform updates to test_count,
// and returns an error if that lock cannot be acquired.
func acquire(ctx context.Context) error {
	path, err := path(lockFile)
	if err != nil {
		return err
	}

	tick := time.NewTicker(acquireTick)
	defer tick.Stop()

	for {
		select {
		case <-tick.C:
			// Try every tick of the above ticker before giving up
			// and trying again.
			_, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0666)
			if err == nil || !os.IsExist(err) {
				return err
			}
		case <-ctx.Done():
			// If the context.Context above has reached its
			// deadline, we must give up.
			return errCouldNotAcquire
		}
	}
}

// release releases the lock file so that another process can take over, or
// returns an error.
func release() error {
	path, err := path(lockFile)
	if err != nil {
		return err
	}
	return os.Remove(path)
}

// callWithCount calls the given countFn with the current count in test_count,
// and updates it with what the function returns.
//
// If the function produced an error, that will be returned instead.
func callWithCount(fn countFn) error {
	path, err := path(countFile)
	if err != nil {
		return err
	}

	f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		return err
	}

	contents, err := ioutil.ReadAll(f)
	if err != nil {
		return err
	}

	var n int = 0
	if len(contents) != 0 {
		n, err = strconv.Atoi(string(contents))
		if err != nil {
			return err
		}
	}

	after, err := fn(n)
	if err != nil {
		return err
	}

	// We want to write over the contents in the file, so "truncate" the
	// file to a length of 0, and then seek to the beginning of the file to
	// update the write head.
	if err := f.Truncate(0); err != nil {
		return err
	}
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		return err
	}

	if _, err := fmt.Fprintf(f, "%d", after); err != nil {
		return err
	}
	return nil
}

// path returns an absolute path corresponding to any given path relative to the
// 't' directory of the current checkout of Git LFS.
func path(s string) (string, error) {
	p := filepath.Join(filepath.Dir(os.Getenv("LFSTEST_DIR")), s)
	if err := os.MkdirAll(filepath.Dir(p), 0666); err != nil {
		return "", err
	}
	return p, nil
}

// fatal reports the given error (if non-nil), and then dies. If the error was
// nil, nothing happens.
func fatal(err error) {
	if err == nil {
		return
	}
	if err := release(); err != nil {
		fmt.Fprintf(os.Stderr, "fatal: while dying, got: %s\n", err)
	}
	fmt.Fprintf(os.Stderr, "fatal: %s\n", err)
	os.Exit(1)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Prometheus 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.

// +build !noloadavg

package collector

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"
)

type loadavgCollector struct {
	metric []prometheus.Gauge
}

func init() {
	Factories["loadavg"] = NewLoadavgCollector
}

// Take a prometheus registry and return a new Collector exposing load average.
func NewLoadavgCollector() (Collector, error) {
	return &loadavgCollector{
		metric: []prometheus.Gauge{
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load1",
				Help:      "1m load average.",
			}),
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load5",
				Help:      "5m load average.",
			}),
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load15",
				Help:      "15m load average.",
			}),
		},
	}, nil
}

func (c *loadavgCollector) Update(ch chan<- prometheus.Metric) (err error) {
	loads, err := getLoad()
	if err != nil {
		return fmt.Errorf("couldn't get load: %s", err)
	}
	for i, load := range loads {
		log.Debugf("Set load %d: %f", i, load)
		c.metric[i].Set(load)
		c.metric[i].Collect(ch)
	}
	return err
}

// Read loadavg from /proc.
func getLoad() (loads []float64, err error) {
	data, err := ioutil.ReadFile(procFilePath("loadavg"))
	if err != nil {
		return nil, err
	}
	loads, err = parseLoad(string(data))
	if err != nil {
		return nil, err
	}
	return loads, nil
}

// Parse /proc loadavg and return 1m, 5m and 15m.
func parseLoad(data string) (loads []float64, err error) {
	loads = make([]float64, 3)
	parts := strings.Fields(data)
	if len(parts) >= 3 {
		for i, load := range parts[0:3] {
			loads[i], err = strconv.ParseFloat(load, 64)
			if err != nil {
				return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
			}
		}
	} else {
		return nil, fmt.Errorf("unexpected content in %s", procFilePath("loadavg"))
	}
	return loads, nil
}
<commit_msg>Reduce nesting in sanity check<commit_after>// Copyright 2015 The Prometheus 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.

// +build !noloadavg

package collector

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"
)

type loadavgCollector struct {
	metric []prometheus.Gauge
}

func init() {
	Factories["loadavg"] = NewLoadavgCollector
}

// Take a prometheus registry and return a new Collector exposing load average.
func NewLoadavgCollector() (Collector, error) {
	return &loadavgCollector{
		metric: []prometheus.Gauge{
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load1",
				Help:      "1m load average.",
			}),
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load5",
				Help:      "5m load average.",
			}),
			prometheus.NewGauge(prometheus.GaugeOpts{
				Namespace: Namespace,
				Name:      "load15",
				Help:      "15m load average.",
			}),
		},
	}, nil
}

func (c *loadavgCollector) Update(ch chan<- prometheus.Metric) (err error) {
	loads, err := getLoad()
	if err != nil {
		return fmt.Errorf("couldn't get load: %s", err)
	}
	for i, load := range loads {
		log.Debugf("Set load %d: %f", i, load)
		c.metric[i].Set(load)
		c.metric[i].Collect(ch)
	}
	return err
}

// Read loadavg from /proc.
func getLoad() (loads []float64, err error) {
	data, err := ioutil.ReadFile(procFilePath("loadavg"))
	if err != nil {
		return nil, err
	}
	loads, err = parseLoad(string(data))
	if err != nil {
		return nil, err
	}
	return loads, nil
}

// Parse /proc loadavg and return 1m, 5m and 15m.
func parseLoad(data string) (loads []float64, err error) {
	loads = make([]float64, 3)
	parts := strings.Fields(data)
	if len(parts) < 3 {
		return nil, fmt.Errorf("unexpected content in %s", procFilePath("loadavg"))
	}
	for i, load := range parts[0:3] {
		loads[i], err = strconv.ParseFloat(load, 64)
		if err != nil {
			return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
		}
	}
	return loads, nil
}
<|endoftext|>


<commit_before>package apiserver

import (
	"net/http"

	"gopkg.in/labstack/echo.v3"
)

// Run starts the HTTP server
func Run() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.Logger.Fatal(e.Start(":1323"))
}
<commit_msg>Add a few API calls<commit_after>package apiserver

import (
	"net/http"

	"../unit"
	state "../unit/state"
	"gopkg.in/labstack/echo.v3"
)

// Run starts the HTTP server
func Run() {
	e := echo.New()
	e.GET("/", getRoot)
	e.GET("/:zone/machines", getMachines)
	e.GET("/:zone/units", getUnits)
	e.PUT("/:zone/command", putCommand)
	e.PUT("/:zone/unit", putUnit)
	e.DELETE("/:zone/unit/:name", deleteUnit)
	e.Logger.Fatal(e.Start(":1323"))
}

func getRoot(c echo.Context) error {
	return c.String(http.StatusOK, "Dispatch API server, if you see this on the internet you're doing it wrong")
}

func getMachines(c echo.Context) error {
	return c.String(http.StatusFailedDependency, "I didn't make a machine a struct? oops.... to be continued")
}

func getUnits(c echo.Context) error {
	units, err := unit.GetAll()
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"status": "error", "error": err.Error()})
	}
	return c.JSON(http.StatusOK, units)
}

func putCommand(c echo.Context) error {
	return c.String(http.StatusTeapot, "Heh, sorry had no time... //TO DO")
}

func putUnit(c echo.Context) error {
	u := unit.New()
	c.Bind(&u) // bind JSON to the unit
	if u.Name == "" || u.UnitContent == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"status": "error", "error": "missing parameters"})
	}
	u.DesiredState = state.Active
	u.SaveOnEtcd()
	u.PutOnQueue()
	return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

func deleteUnit(c echo.Context) error {
	name := c.FormValue("name")
	if name == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"status": "error", "error": "missing name"})
	}
	u := unit.NewFromEtcd(name)
	if u.Name == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"status": "error", "error": "unit does not exist"})
	}
	u.Destroy()
	return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 persist asynchronously writes changes in the map (name->idx) to
// file.
package persist

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"sync"
	"time"

	"github.com/ligato/cn-infra/logging"
	log "github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/vpp-agent/pkg/idxvpp"
	"github.com/ligato/vpp-agent/pkg/idxvppxvpp/nametoidx"
)

var (
	// configuration file path
	idxMapConfigFile string

	// configuration common to all mappings
	gConfig *nametoidx.Config
)

// init serves for parsing the program's arguments.
func init() {
	flag.StringVar(&idxMapConfigFile, "idxmap-config", "",
		"Location of the configuration file for index-to-name maps; also set via 'IDXMAP_CONFIG' env variable.")

	if gConfig == nil {
		var err error
		gConfig, err = nametoidx.ConfigFromFile(idxMapConfigFile)
		if err != nil {
			log.DefaultLogger().WithFields(logging.Fields{"filepath": idxMapConfigFile, "err": err}).Warn(
				"Failed to load idxmap configuration file")
		} else {
			log.DefaultLogger().WithFields(logging.Fields{"filepath": idxMapConfigFile}).Debug(
				"Loaded idxmap configuration file")
		}
	}
}

// Marshalling loads the config and starts watching for changes.
func Marshalling(agentLabel string, idxMap idxvpp.NameToIdx, loadedFromFile idxvpp.NameToIdxRW) error {
	log.DefaultLogger().Debug("Persistence")

	changes := make(chan idxvpp.NameToIdxDto, 1000)
	fileName := idxMap.GetRegistryTitle() + ".json"
	persist := NewNameToIdxPersist(fileName, gConfig, agentLabel, changes)
	err := persist.Init()
	if err != nil {
		return err
	}

	idxMap.Watch("idxpersist", nametoidx.ToChan(changes))

	err = persist.loadIdxMapFile(loadedFromFile)
	if err != nil {
		return err
	}

	return nil
}

// NameToIdxPersist is a decorator for NameToIdxRW implementing persistent storage.
type NameToIdxPersist struct {
	// registrations notifies about the changes made in the mapping to be persisted
	registrations chan idxvpp.NameToIdxDto

	// Configuration associated with this mapping.
	// Unless this is a unit test, it is the same as the global configuration.
	config *nametoidx.Config

	// (de)serialization of mapping data
	nameToIdx map[string]uint32

	// persistent storage location
	fileDir  string
	filePath string

	// Synchronization between the underlying registry and the persistent storage.
	syncLock  sync.Mutex
	syncCh    chan bool
	syncAckCh chan error
}

// NewNameToIdxPersist initializes decorator for persistent storage of index-to-name mapping.
func NewNameToIdxPersist(fileName string, config *nametoidx.Config, namespace string,
	registrations chan idxvpp.NameToIdxDto) *NameToIdxPersist {

	persist := NameToIdxPersist{}
	persist.config = config
	persist.nameToIdx = map[string]uint32{}

	persist.fileDir = path.Join(persist.config.PersistentStorage.Location, namespace)
	persist.filePath = path.Join(persist.fileDir, fileName)

	persist.registrations = registrations

	return &persist
}

// Init starts Go routine that watches chan idxvpp.NameToIdxDto.
func (persist *NameToIdxPersist) Init() error {
	persist.syncCh = make(chan bool)
	persist.syncAckCh = make(chan error)

	offset := rand.Int63n(int64(persist.config.PersistentStorage.MaxSyncStartDelay))
	go persist.periodicIdxMapSync(time.Duration(offset))

	return nil
}

// loadIdxMapFile loads persistently stored entries of the associated registry.
func (persist *NameToIdxPersist) loadIdxMapFile(loadedFromFile idxvpp.NameToIdxRW) error {
	if _, err := os.Stat(persist.filePath); os.IsNotExist(err) {
		log.DefaultLogger().WithFields(logging.Fields{"Filepath": persist.filePath}).Debug(
			"Persistent storage for name-to-index mapping doesn't exist yet")
		return nil
	}
	idxMapData, err := ioutil.ReadFile(persist.filePath)
	if err != nil {
		return err
	}

	err = json.Unmarshal(idxMapData, &persist.nameToIdx)
	if err != nil {
		return err
	}

	for name, idx := range persist.nameToIdx {
		loadedFromFile.RegisterName(name, idx, nil)
	}
	return nil
}

// periodicIdxMapSync periodically synchronizes the underlying registry with the persistent storage.
func (persist *NameToIdxPersist) periodicIdxMapSync(offset time.Duration) error {
	for {
		select {
		case reg := <-persist.registrations:
			if reg.Del {
				persist.unregisterName(reg.Name)
			} else {
				persist.registerName(reg.Name, reg.Idx)
			}
		case <-persist.syncCh:
			persist.syncAckCh <- persist.syncMapping()
		case <-time.After(persist.config.PersistentStorage.SyncInterval + offset):
			offset = 0
			err := persist.syncMapping()
			if err != nil {
				log.DefaultLogger().WithFields(logging.Fields{"Error": err, "Filepath": persist.filePath}).Error(
					"Failed to sync idxMap with the persistent storage")
			}
		}
	}
}

// syncMapping updates the persistent storage with the new mappings.
// Current implementation simply re-builds the file content from the scratch.
// TODO: NICE-TO-HAVE incremental update
func (persist *NameToIdxPersist) syncMapping() error {
	persist.syncLock.Lock()
	defer persist.syncLock.Unlock()

	idxMapData, err := json.Marshal(persist.nameToIdx)
	if err != nil {
		return err
	}

	err = os.MkdirAll(persist.fileDir, 0777)
	if err != nil {
		return err
	}

	//log.Debug("Persist len=", len(persist.nameToIdx)," ", persist.filePath)

	return ioutil.WriteFile(persist.filePath, idxMapData, 0644)
}

// RegisterName from NameToIdxPersist allows to add a name-to-index mapping into both the underlying registry and
// the persistent storage (with some delay in synchronization).
func (persist *NameToIdxPersist) registerName(name string, idx uint32) {
	persist.nameToIdx[name] = idx
}

// UnregisterName from NameToIdxPersist allows to remove mapping from both
// the underlying registry and the persistent storage.
func (persist *NameToIdxPersist) unregisterName(name string) {
	delete(persist.nameToIdx, name)
}

// Close triggers explicit synchronization and closes the underlying mapping.
func (persist *NameToIdxPersist) Close() error {
	err := persist.Sync()
	if err != nil {
		return err
	}
	return nil
}

// Sync triggers immediate synchronization between the underlying registry and the persistent storage.
// The function doesn't return until the operation has fully finished.
func (persist *NameToIdxPersist) Sync() error {
	persist.syncCh <- true
	return <-persist.syncAckCh
}
<commit_msg>Fix import<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 persist asynchronously writes changes in the map (name->idx) to
// file.
package persist

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"sync"
	"time"

	"github.com/ligato/cn-infra/logging"
	log "github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/vpp-agent/pkg/idxvpp"
	"github.com/ligato/vpp-agent/pkg/idxvpp/nametoidx"
)

var (
	// configuration file path
	idxMapConfigFile string

	// configuration common to all mappings
	gConfig *nametoidx.Config
)

// init serves for parsing the program's arguments.
func init() {
	flag.StringVar(&idxMapConfigFile, "idxmap-config", "",
		"Location of the configuration file for index-to-name maps; also set via 'IDXMAP_CONFIG' env variable.")

	if gConfig == nil {
		var err error
		gConfig, err = nametoidx.ConfigFromFile(idxMapConfigFile)
		if err != nil {
			log.DefaultLogger().WithFields(logging.Fields{"filepath": idxMapConfigFile, "err": err}).Warn(
				"Failed to load idxmap configuration file")
		} else {
			log.DefaultLogger().WithFields(logging.Fields{"filepath": idxMapConfigFile}).Debug(
				"Loaded idxmap configuration file")
		}
	}
}

// Marshalling loads the config and starts watching for changes.
func Marshalling(agentLabel string, idxMap idxvpp.NameToIdx, loadedFromFile idxvpp.NameToIdxRW) error {
	log.DefaultLogger().Debug("Persistence")

	changes := make(chan idxvpp.NameToIdxDto, 1000)
	fileName := idxMap.GetRegistryTitle() + ".json"
	persist := NewNameToIdxPersist(fileName, gConfig, agentLabel, changes)
	err := persist.Init()
	if err != nil {
		return err
	}

	idxMap.Watch("idxpersist", nametoidx.ToChan(changes))

	err = persist.loadIdxMapFile(loadedFromFile)
	if err != nil {
		return err
	}

	return nil
}

// NameToIdxPersist is a decorator for NameToIdxRW implementing persistent storage.
type NameToIdxPersist struct {
	// registrations notifies about the changes made in the mapping to be persisted
	registrations chan idxvpp.NameToIdxDto

	// Configuration associated with this mapping.
	// Unless this is a unit test, it is the same as the global configuration.
	config *nametoidx.Config

	// (de)serialization of mapping data
	nameToIdx map[string]uint32

	// persistent storage location
	fileDir  string
	filePath string

	// Synchronization between the underlying registry and the persistent storage.
	syncLock  sync.Mutex
	syncCh    chan bool
	syncAckCh chan error
}

// NewNameToIdxPersist initializes decorator for persistent storage of index-to-name mapping.
func NewNameToIdxPersist(fileName string, config *nametoidx.Config, namespace string,
	registrations chan idxvpp.NameToIdxDto) *NameToIdxPersist {

	persist := NameToIdxPersist{}
	persist.config = config
	persist.nameToIdx = map[string]uint32{}

	persist.fileDir = path.Join(persist.config.PersistentStorage.Location, namespace)
	persist.filePath = path.Join(persist.fileDir, fileName)

	persist.registrations = registrations

	return &persist
}

// Init starts Go routine that watches chan idxvpp.NameToIdxDto.
func (persist *NameToIdxPersist) Init() error {
	persist.syncCh = make(chan bool)
	persist.syncAckCh = make(chan error)

	offset := rand.Int63n(int64(persist.config.PersistentStorage.MaxSyncStartDelay))
	go persist.periodicIdxMapSync(time.Duration(offset))

	return nil
}

// loadIdxMapFile loads persistently stored entries of the associated registry.
func (persist *NameToIdxPersist) loadIdxMapFile(loadedFromFile idxvpp.NameToIdxRW) error {
	if _, err := os.Stat(persist.filePath); os.IsNotExist(err) {
		log.DefaultLogger().WithFields(logging.Fields{"Filepath": persist.filePath}).Debug(
			"Persistent storage for name-to-index mapping doesn't exist yet")
		return nil
	}
	idxMapData, err := ioutil.ReadFile(persist.filePath)
	if err != nil {
		return err
	}

	err = json.Unmarshal(idxMapData, &persist.nameToIdx)
	if err != nil {
		return err
	}

	for name, idx := range persist.nameToIdx {
		loadedFromFile.RegisterName(name, idx, nil)
	}
	return nil
}

// periodicIdxMapSync periodically synchronizes the underlying registry with the persistent storage.
func (persist *NameToIdxPersist) periodicIdxMapSync(offset time.Duration) error {
	for {
		select {
		case reg := <-persist.registrations:
			if reg.Del {
				persist.unregisterName(reg.Name)
			} else {
				persist.registerName(reg.Name, reg.Idx)
			}
		case <-persist.syncCh:
			persist.syncAckCh <- persist.syncMapping()
		case <-time.After(persist.config.PersistentStorage.SyncInterval + offset):
			offset = 0
			err := persist.syncMapping()
			if err != nil {
				log.DefaultLogger().WithFields(logging.Fields{"Error": err, "Filepath": persist.filePath}).Error(
					"Failed to sync idxMap with the persistent storage")
			}
		}
	}
}

// syncMapping updates the persistent storage with the new mappings.
// Current implementation simply re-builds the file content from the scratch.
// TODO: NICE-TO-HAVE incremental update
func (persist *NameToIdxPersist) syncMapping() error {
	persist.syncLock.Lock()
	defer persist.syncLock.Unlock()

	idxMapData, err := json.Marshal(persist.nameToIdx)
	if err != nil {
		return err
	}

	err = os.MkdirAll(persist.fileDir, 0777)
	if err != nil {
		return err
	}

	//log.Debug("Persist len=", len(persist.nameToIdx)," ", persist.filePath)

	return ioutil.WriteFile(persist.filePath, idxMapData, 0644)
}

// RegisterName from NameToIdxPersist allows to add a name-to-index mapping into both the underlying registry and
// the persistent storage (with some delay in synchronization).
func (persist *NameToIdxPersist) registerName(name string, idx uint32) {
	persist.nameToIdx[name] = idx
}

// UnregisterName from NameToIdxPersist allows to remove mapping from both
// the underlying registry and the persistent storage.
func (persist *NameToIdxPersist) unregisterName(name string) {
	delete(persist.nameToIdx, name)
}

// Close triggers explicit synchronization and closes the underlying mapping.
func (persist *NameToIdxPersist) Close() error {
	err := persist.Sync()
	if err != nil {
		return err
	}
	return nil
}

// Sync triggers immediate synchronization between the underlying registry and the persistent storage.
// The function doesn't return until the operation has fully finished.
func (persist *NameToIdxPersist) Sync() error {
	persist.syncCh <- true
	return <-persist.syncAckCh
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Rook 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.
*/

// Package mgr for the Ceph manager.
package mgr

import (
	"fmt"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/daemon/ceph/client"
)

const (
	orchestratorModuleName = "orchestrator_cli"
	rookModuleName         = "rook"
)

// Ceph docs about the orchestrator modules: http://docs.ceph.com/docs/master/mgr/orchestrator_cli/
func (c *Cluster) configureOrchestratorModules() error {
	if !cephv1.VersionAtLeast(c.cephVersion.Name, cephv1.Nautilus) {
		logger.Infof("skipping enabling orchestrator modules on releases older than nautilus")
		return nil
	}

	if err := client.MgrEnableModule(c.context, c.Namespace, orchestratorModuleName, true); err != nil {
		return fmt.Errorf("failed to enable mgr orchestrator module. %+v", err)
	}
	if err := client.MgrEnableModule(c.context, c.Namespace, rookModuleName, true); err != nil {
		return fmt.Errorf("failed to enable mgr rook module. %+v", err)
	}
	if _, err := client.ExecuteCephCommand(c.context, c.Namespace, []string{"orchestrator", "set", "backend", "rook"}); err != nil {
		return fmt.Errorf("failed to set rook as the orchestrator backend. %+v", err)
	}

	return nil
}
<commit_msg>ceph: retry "ceph orchestrator set backend rook" if command returns EINVAL<commit_after>/*
Copyright 2018 The Rook 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.
*/

// Package mgr for the Ceph manager.
package mgr

import (
	"fmt"
	"time"

	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/daemon/ceph/client"
)

const (
	orchestratorModuleName = "orchestrator_cli"
	rookModuleName         = "rook"
)

var (
	orchestratorInitWaitTime = 5 * time.Second
)

// Ceph docs about the orchestrator modules: http://docs.ceph.com/docs/master/mgr/orchestrator_cli/
func (c *Cluster) configureOrchestratorModules() error {
	if !cephv1.VersionAtLeast(c.cephVersion.Name, cephv1.Nautilus) {
		logger.Infof("skipping enabling orchestrator modules on releases older than nautilus")
		return nil
	}

	if err := client.MgrEnableModule(c.context, c.Namespace, orchestratorModuleName, true); err != nil {
		return fmt.Errorf("failed to enable mgr orchestrator module. %+v", err)
	}
	if err := client.MgrEnableModule(c.context, c.Namespace, rookModuleName, true); err != nil {
		return fmt.Errorf("failed to enable mgr rook module. %+v", err)
	}
	// retry a few times in the case that the mgr module is not ready to accept commands
	for i := 0; i < 5; i++ {
		_, err := client.ExecuteCephCommand(c.context, c.Namespace, []string{"orchestrator", "set", "backend", "rook"})
		if err != nil {
			exitCode, parsed := c.exitCode(err)
			if parsed {
				if exitCode == invalidArgErrorCode {
					logger.Infof("orchestrator module is not ready yet. trying again...")
					time.Sleep(orchestratorInitWaitTime)
					continue
				}
			}
			return fmt.Errorf("failed to set rook as the orchestrator backend. %+v", err)
		}
		break
	}

	return nil
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strconv"
	"testing"
	"time"

	"github.com/hashicorp/consul/consul/structs"
	"github.com/hashicorp/consul/testutil"
)

func makeHTTPServer(t *testing.T) (string, *HTTPServer) {
	conf := nextConfig()
	dir, agent := makeAgent(t, conf)
	uiDir := filepath.Join(dir, "ui")
	if err := os.Mkdir(uiDir, 755); err != nil {
		t.Fatalf("err: %v", err)
	}
	conf.UiDir = uiDir
	servers, err := NewHTTPServers(agent, conf, agent.logOutput)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(servers) == 0 {
		t.Fatalf(fmt.Sprintf("Failed to make HTTP server"))
	}
	return dir, servers[0]
}

func encodeReq(obj interface{}) io.ReadCloser {
	buf := bytes.NewBuffer(nil)
	enc := json.NewEncoder(buf)
	enc.Encode(obj)
	return ioutil.NopCloser(buf)
}

func TestSetIndex(t *testing.T) {
	resp := httptest.NewRecorder()
	setIndex(resp, 1000)
	header := resp.Header().Get("X-Consul-Index")
	if header != "1000" {
		t.Fatalf("Bad: %v", header)
	}
	setIndex(resp, 2000)
	if v := resp.Header()["X-Consul-Index"]; len(v) != 1 {
		t.Fatalf("bad: %#v", v)
	}
}

func TestSetKnownLeader(t *testing.T) {
	resp := httptest.NewRecorder()
	setKnownLeader(resp, true)
	header := resp.Header().Get("X-Consul-KnownLeader")
	if header != "true" {
		t.Fatalf("Bad: %v", header)
	}
	resp = httptest.NewRecorder()
	setKnownLeader(resp, false)
	header = resp.Header().Get("X-Consul-KnownLeader")
	if header != "false" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestSetLastContact(t *testing.T) {
	resp := httptest.NewRecorder()
	setLastContact(resp, 123456*time.Microsecond)
	header := resp.Header().Get("X-Consul-LastContact")
	if header != "123" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestSetMeta(t *testing.T) {
	meta := structs.QueryMeta{
		Index:       1000,
		KnownLeader: true,
		LastContact: 123456 * time.Microsecond,
	}
	resp := httptest.NewRecorder()
	setMeta(resp, &meta)
	header := resp.Header().Get("X-Consul-Index")
	if header != "1000" {
		t.Fatalf("Bad: %v", header)
	}
	header = resp.Header().Get("X-Consul-KnownLeader")
	if header != "true" {
		t.Fatalf("Bad: %v", header)
	}
	header = resp.Header().Get("X-Consul-LastContact")
	if header != "123" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestContentTypeIsJSON(t *testing.T) {
	dir, srv := makeHTTPServer(t)

	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer srv.agent.Shutdown()

	resp := httptest.NewRecorder()

	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
		// stub out a DirEntry so that it will be encoded as JSON
		return &structs.DirEntry{Key: "key"}, nil
	}

	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
	srv.wrap(handler)(resp, req)

	contentType := resp.Header().Get("Content-Type")

	if contentType != "application/json" {
		t.Fatalf("Content-Type header was not 'application/json'")
	}
}

func TestParseWait(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60s&index=1000", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if b.MinQueryIndex != 1000 {
		t.Fatalf("Bad: %v", b)
	}
	if b.MaxQueryTime != 60*time.Second {
		t.Fatalf("Bad: %v", b)
	}
}

func TestParseWait_InvalidTime(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60foo&index=1000", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

func TestParseWait_InvalidIndex(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60s&index=foo", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

func TestParseConsistency(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?stale", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if !b.AllowStale {
		t.Fatalf("Bad: %v", b)
	}
	if b.RequireConsistent {
		t.Fatalf("Bad: %v", b)
	}

	b = structs.QueryOptions{}
	req, err = http.NewRequest("GET",
		"/v1/catalog/nodes?consistent", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if b.AllowStale {
		t.Fatalf("Bad: %v", b)
	}
	if !b.RequireConsistent {
		t.Fatalf("Bad: %v", b)
	}
}

func TestParseConsistency_Invalid(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?stale&consistent", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

// assertIndex tests that X-Consul-Index is set and non-zero
func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" || header == "0" {
		t.Fatalf("Bad: %v", header)
	}
}

// checkIndex is like assertIndex but returns an error
func checkIndex(resp *httptest.ResponseRecorder) error {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" || header == "0" {
		return fmt.Errorf("Bad: %v", header)
	}
	return nil
}

// getIndex parses X-Consul-Index
func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" {
		t.Fatalf("Bad: %v", header)
	}
	val, err := strconv.Atoi(header)
	if err != nil {
		t.Fatalf("Bad: %v", header)
	}
	return uint64(val)
}

func httpTest(t *testing.T, f func(srv *HTTPServer)) {
	dir, srv := makeHTTPServer(t)
	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer srv.agent.Shutdown()
	testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
	f(srv)
}
<commit_msg>agent: test pretty printed json<commit_after>package agent

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strconv"
	"testing"
	"time"

	"github.com/hashicorp/consul/consul/structs"
	"github.com/hashicorp/consul/testutil"
)

func makeHTTPServer(t *testing.T) (string, *HTTPServer) {
	conf := nextConfig()
	dir, agent := makeAgent(t, conf)
	uiDir := filepath.Join(dir, "ui")
	if err := os.Mkdir(uiDir, 755); err != nil {
		t.Fatalf("err: %v", err)
	}
	conf.UiDir = uiDir
	servers, err := NewHTTPServers(agent, conf, agent.logOutput)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(servers) == 0 {
		t.Fatalf(fmt.Sprintf("Failed to make HTTP server"))
	}
	return dir, servers[0]
}

func encodeReq(obj interface{}) io.ReadCloser {
	buf := bytes.NewBuffer(nil)
	enc := json.NewEncoder(buf)
	enc.Encode(obj)
	return ioutil.NopCloser(buf)
}

func TestSetIndex(t *testing.T) {
	resp := httptest.NewRecorder()
	setIndex(resp, 1000)
	header := resp.Header().Get("X-Consul-Index")
	if header != "1000" {
		t.Fatalf("Bad: %v", header)
	}
	setIndex(resp, 2000)
	if v := resp.Header()["X-Consul-Index"]; len(v) != 1 {
		t.Fatalf("bad: %#v", v)
	}
}

func TestSetKnownLeader(t *testing.T) {
	resp := httptest.NewRecorder()
	setKnownLeader(resp, true)
	header := resp.Header().Get("X-Consul-KnownLeader")
	if header != "true" {
		t.Fatalf("Bad: %v", header)
	}
	resp = httptest.NewRecorder()
	setKnownLeader(resp, false)
	header = resp.Header().Get("X-Consul-KnownLeader")
	if header != "false" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestSetLastContact(t *testing.T) {
	resp := httptest.NewRecorder()
	setLastContact(resp, 123456*time.Microsecond)
	header := resp.Header().Get("X-Consul-LastContact")
	if header != "123" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestSetMeta(t *testing.T) {
	meta := structs.QueryMeta{
		Index:       1000,
		KnownLeader: true,
		LastContact: 123456 * time.Microsecond,
	}
	resp := httptest.NewRecorder()
	setMeta(resp, &meta)
	header := resp.Header().Get("X-Consul-Index")
	if header != "1000" {
		t.Fatalf("Bad: %v", header)
	}
	header = resp.Header().Get("X-Consul-KnownLeader")
	if header != "true" {
		t.Fatalf("Bad: %v", header)
	}
	header = resp.Header().Get("X-Consul-LastContact")
	if header != "123" {
		t.Fatalf("Bad: %v", header)
	}
}

func TestContentTypeIsJSON(t *testing.T) {
	dir, srv := makeHTTPServer(t)

	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer srv.agent.Shutdown()

	resp := httptest.NewRecorder()

	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
		// stub out a DirEntry so that it will be encoded as JSON
		return &structs.DirEntry{Key: "key"}, nil
	}

	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
	srv.wrap(handler)(resp, req)

	contentType := resp.Header().Get("Content-Type")

	if contentType != "application/json" {
		t.Fatalf("Content-Type header was not 'application/json'")
	}
}

func TestPrettyPrint(t *testing.T) {
	dir, srv := makeHTTPServer(t)
	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer srv.agent.Shutdown()

	r := &structs.DirEntry{Key: "key"}

	resp := httptest.NewRecorder()
	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
		return r, nil
	}

	req, _ := http.NewRequest("GET", "/v1/kv/key?pretty=1", nil)
	srv.wrap(handler)(resp, req)

	expected, _ := json.MarshalIndent(r, "", "    ")
	actual, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if !bytes.Equal(expected, actual) {
		t.Fatalf("bad: %q", string(actual))
	}
}

func TestParseWait(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60s&index=1000", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if b.MinQueryIndex != 1000 {
		t.Fatalf("Bad: %v", b)
	}
	if b.MaxQueryTime != 60*time.Second {
		t.Fatalf("Bad: %v", b)
	}
}

func TestParseWait_InvalidTime(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60foo&index=1000", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

func TestParseWait_InvalidIndex(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?wait=60s&index=foo", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseWait(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

func TestParseConsistency(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?stale", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if !b.AllowStale {
		t.Fatalf("Bad: %v", b)
	}
	if b.RequireConsistent {
		t.Fatalf("Bad: %v", b)
	}

	b = structs.QueryOptions{}
	req, err = http.NewRequest("GET",
		"/v1/catalog/nodes?consistent", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); d {
		t.Fatalf("unexpected done")
	}

	if b.AllowStale {
		t.Fatalf("Bad: %v", b)
	}
	if !b.RequireConsistent {
		t.Fatalf("Bad: %v", b)
	}
}

func TestParseConsistency_Invalid(t *testing.T) {
	resp := httptest.NewRecorder()
	var b structs.QueryOptions

	req, err := http.NewRequest("GET",
		"/v1/catalog/nodes?stale&consistent", nil)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if d := parseConsistency(resp, req, &b); !d {
		t.Fatalf("expected done")
	}

	if resp.Code != 400 {
		t.Fatalf("bad code: %v", resp.Code)
	}
}

// assertIndex tests that X-Consul-Index is set and non-zero
func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" || header == "0" {
		t.Fatalf("Bad: %v", header)
	}
}

// checkIndex is like assertIndex but returns an error
func checkIndex(resp *httptest.ResponseRecorder) error {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" || header == "0" {
		return fmt.Errorf("Bad: %v", header)
	}
	return nil
}

// getIndex parses X-Consul-Index
func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 {
	header := resp.Header().Get("X-Consul-Index")
	if header == "" {
		t.Fatalf("Bad: %v", header)
	}
	val, err := strconv.Atoi(header)
	if err != nil {
		t.Fatalf("Bad: %v", header)
	}
	return uint64(val)
}

func httpTest(t *testing.T, f func(srv *HTTPServer)) {
	dir, srv := makeHTTPServer(t)
	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer srv.agent.Shutdown()
	testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
	f(srv)
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"io"
	"os"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/filepathfilter"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/tools"
	"github.com/spf13/cobra"
)

var (
	// smudgeSkip is a command-line flag belonging to the "git-lfs smudge"
	// command specifying whether to skip the smudge process.
	smudgeSkip = false
)

// smudge smudges the given `*lfs.Pointer`, "ptr", and writes its objects
// contents to the `io.Writer`, "to".
//
// If the encoded LFS pointer is not parse-able as a pointer, the contents of
// that file will instead be spooled to a temporary location on disk and then
// copied out back to Git.
//
// If the smudged object did not "pass" the include and exclude filterset, it
// will not be downloaded, and the object will remain a pointer on disk, as if
// the smudge filter had not been applied at all.
//
// Any errors encountered along the way will be returned immediately if they
// were non-fatal, otherwise execution will halt and the process will be
// terminated by using the `commands.Panic()` func.
func smudge(to io.Writer, from io.Reader, filename string, skip bool, filter *filepathfilter.Filter) error {
	ptr, pbuf, perr := lfs.DecodeFrom(from)
	if perr != nil {
		name := fmt.Sprintf("%s-malformed", filename)
		if _, err := tools.SpoolName(to, pbuf, name); err != nil {
			return errors.Wrap(err, perr.Error())
		}

		return errors.NewNotAPointerError(errors.Errorf(
			"Unable to parse pointer at: %q", filename,
		))
	}

	lfs.LinkOrCopyFromReference(ptr.Oid, ptr.Size)
	cb, file, err := lfs.CopyCallbackFile("smudge", filename, 1, 1)
	if err != nil {
		return err
	}

	download := !skip
	if download {
		download = filter.Allows(filename)
	}

	err = ptr.Smudge(to, filename, download, getTransferManifest(), cb)
	if file != nil {
		file.Close()
	}

	if err != nil {
		ptr.Encode(to)
		// Download declined error is ok to skip if we weren't requesting download
		if !(errors.IsDownloadDeclinedError(err) && !download) {
			LoggedError(err, "Error downloading object: %s (%s)", filename, ptr.Oid)
			if !cfg.SkipDownloadErrors() {
				os.Exit(2)
			}
		}
	}

	return nil
}

func smudgeCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'smudge' filter")
	lfs.InstallHooks(false)

	if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
		smudgeSkip = true
	}
	filter := filepathfilter.New(cfg.FetchIncludePaths(), cfg.FetchExcludePaths())

	if err := smudge(os.Stdout, os.Stdin, smudgeFilename(args), smudgeSkip, filter); err != nil {
		if errors.IsNotAPointerError(err) {
			fmt.Fprintln(os.Stderr, err.Error())
		} else {
			Error(err.Error())
		}
	}
}

func smudgeFilename(args []string) string {
	if len(args) > 0 {
		return args[0]
	}
	return "<unknown file>"
}

func init() {
	RegisterCommand("smudge", smudgeCommand, func(cmd *cobra.Command) {
		cmd.Flags().BoolVarP(&smudgeSkip, "skip", "s", false, "")
	})
}
<commit_msg>commands/smudge: default to valid filename for missing smudge arguments<commit_after>package commands

import (
	"fmt"
	"io"
	"os"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/filepathfilter"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/tools"
	"github.com/spf13/cobra"
)

var (
	// smudgeSkip is a command-line flag belonging to the "git-lfs smudge"
	// command specifying whether to skip the smudge process.
	smudgeSkip = false
)

// smudge smudges the given `*lfs.Pointer`, "ptr", and writes its objects
// contents to the `io.Writer`, "to".
//
// If the encoded LFS pointer is not parse-able as a pointer, the contents of
// that file will instead be spooled to a temporary location on disk and then
// copied out back to Git.
//
// If the smudged object did not "pass" the include and exclude filterset, it
// will not be downloaded, and the object will remain a pointer on disk, as if
// the smudge filter had not been applied at all.
//
// Any errors encountered along the way will be returned immediately if they
// were non-fatal, otherwise execution will halt and the process will be
// terminated by using the `commands.Panic()` func.
func smudge(to io.Writer, from io.Reader, filename string, skip bool, filter *filepathfilter.Filter) error {
	ptr, pbuf, perr := lfs.DecodeFrom(from)
	if perr != nil {
		name := fmt.Sprintf("%s-malformed", filename)
		if _, err := tools.SpoolName(to, pbuf, name); err != nil {
			return errors.Wrap(err, perr.Error())
		}

		return errors.NewNotAPointerError(errors.Errorf(
			"Unable to parse pointer at: %q", filename,
		))
	}

	lfs.LinkOrCopyFromReference(ptr.Oid, ptr.Size)
	cb, file, err := lfs.CopyCallbackFile("smudge", filename, 1, 1)
	if err != nil {
		return err
	}

	download := !skip
	if download {
		download = filter.Allows(filename)
	}

	err = ptr.Smudge(to, filename, download, getTransferManifest(), cb)
	if file != nil {
		file.Close()
	}

	if err != nil {
		ptr.Encode(to)
		// Download declined error is ok to skip if we weren't requesting download
		if !(errors.IsDownloadDeclinedError(err) && !download) {
			LoggedError(err, "Error downloading object: %s (%s)", filename, ptr.Oid)
			if !cfg.SkipDownloadErrors() {
				os.Exit(2)
			}
		}
	}

	return nil
}

func smudgeCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'smudge' filter")
	lfs.InstallHooks(false)

	if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
		smudgeSkip = true
	}
	filter := filepathfilter.New(cfg.FetchIncludePaths(), cfg.FetchExcludePaths())

	if err := smudge(os.Stdout, os.Stdin, smudgeFilename(args), smudgeSkip, filter); err != nil {
		if errors.IsNotAPointerError(err) {
			fmt.Fprintln(os.Stderr, err.Error())
		} else {
			Error(err.Error())
		}
	}
}

func smudgeFilename(args []string) string {
	if len(args) > 0 {
		return args[0]
	}
	return "unknown"
}

func init() {
	RegisterCommand("smudge", smudgeCommand, func(cmd *cobra.Command) {
		cmd.Flags().BoolVarP(&smudgeSkip, "skip", "s", false, "")
	})
}
<|endoftext|>


<commit_before>package somepackage

import (
	"bytes"
	"go/format"
	"os"
	"path/filepath"
	"testing"
)

func TestFormatFiles(t *testing.T) {
	// Find the paths of all input files in the data directory.
	paths, err := filepath.Glob(filepath.Join("testdata", "*.input"))
	if err != nil {
		t.Fatal(err)
	}

	for _, path := range paths {
		_, filename := filepath.Split(path)
		testname := filename[:len(filename)-len(filepath.Ext(path))]

		// Each path turns into a test: the test name is the filename without the
		// extension.
		t.Run(testname, func(t *testing.T) {
			source, err := os.ReadFile(path)
			if err != nil {
				t.Fatal("error reading source file:", err)
			}

			// >>> This is the actual code under test.
			output, err := format.Source(source)
			if err != nil {
				t.Fatal("error formatting:", err)
			}
			// <<<

			// Each input file is expected to have a "golden output" file, with the
			// same path except the .input extension is replaced by .golden
			goldenfile := filepath.Join("testdata", testname+".golden")
			want, err := os.ReadFile(goldenfile)
			if err != nil {
				t.Fatal("error reading golden file:", err)
			}

			if !bytes.Equal(output, want) {
				t.Errorf("\n==== got:\n%s\n==== want:\n%s\n", output, want)
			}
		})
	}
}
<commit_msg>Add copyright comment<commit_after>// Demonstrates file-driven tests in Go.
//
// Eli Bendersky [https://eli.thegreenplace.net]
// This code is in the public domain.
package somepackage

import (
	"bytes"
	"go/format"
	"os"
	"path/filepath"
	"testing"
)

func TestFormatFiles(t *testing.T) {
	// Find the paths of all input files in the data directory.
	paths, err := filepath.Glob(filepath.Join("testdata", "*.input"))
	if err != nil {
		t.Fatal(err)
	}

	for _, path := range paths {
		_, filename := filepath.Split(path)
		testname := filename[:len(filename)-len(filepath.Ext(path))]

		// Each path turns into a test: the test name is the filename without the
		// extension.
		t.Run(testname, func(t *testing.T) {
			source, err := os.ReadFile(path)
			if err != nil {
				t.Fatal("error reading source file:", err)
			}

			// >>> This is the actual code under test.
			output, err := format.Source(source)
			if err != nil {
				t.Fatal("error formatting:", err)
			}
			// <<<

			// Each input file is expected to have a "golden output" file, with the
			// same path except the .input extension is replaced by .golden
			goldenfile := filepath.Join("testdata", testname+".golden")
			want, err := os.ReadFile(goldenfile)
			if err != nil {
				t.Fatal("error reading golden file:", err)
			}

			if !bytes.Equal(output, want) {
				t.Errorf("\n==== got:\n%s\n==== want:\n%s\n", output, want)
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// 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 exec

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"sort"
	"sync"
	"time"

	"github.com/coreos/ignition/config"
	"github.com/coreos/ignition/src/exec/stages"
	"github.com/coreos/ignition/src/log"
	"github.com/coreos/ignition/src/providers"
)

const (
	DefaultFetchTimeout = time.Minute
)

var (
	ErrNoProviders = errors.New("no config providers were online")
	ErrTimeout     = errors.New("timed out while waiting for a config provider to come online")
)

type Engine struct {
	ConfigCache  string
	FetchTimeout time.Duration
	Logger       log.Logger
	Root         string
	providers    map[string]providers.Provider
}

func (e *Engine) AddProvider(provider providers.Provider) {
	if e.providers == nil {
		e.providers = map[string]providers.Provider{}
	}
	e.providers[provider.Name()] = provider
}

func (e Engine) Providers() []providers.Provider {
	keys := []string{}
	for key := range e.providers {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	providers := make([]providers.Provider, 0, len(e.providers))
	for _, key := range keys {
		providers = append(providers, e.providers[key])
	}
	return providers
}

func (e Engine) Run(stageName string) bool {
	config, err := e.acquireConfig()
	if err != nil {
		e.Logger.Crit("failed to acquire config: %v", err)
		return false
	}

	e.Logger.PushPrefix(stageName)
	defer e.Logger.PopPrefix()
	return stages.Get(stageName).Create(&e.Logger, e.Root).Run(config)
}

func (e Engine) acquireConfig() (cfg config.Config, err error) {
	// First try read the config @ e.ConfigCache.
	b, err := ioutil.ReadFile(e.ConfigCache)
	if err == nil {
		if err = json.Unmarshal(b, &cfg); err != nil {
			e.Logger.Crit("failed to parse cached config: %v", err)
		}
		return
	}

	// (Re)Fetch the config if the cache is unreadable.
	cfg, err = fetchConfig(e.Providers(), e.FetchTimeout)
	if err != nil {
		e.Logger.Crit("failed to fetch config: %v", err)
		return
	}
	e.Logger.Debug("fetched config: %+v", cfg)

	// Populate the config cache.
	b, err = json.Marshal(cfg)
	if err != nil {
		e.Logger.Crit("failed to marshal cached config: %v", err)
		return
	}
	if err = ioutil.WriteFile(e.ConfigCache, b, 0640); err != nil {
		e.Logger.Crit("failed to write cached config: %v", err)
		return
	}

	return
}

func fetchConfig(providers []providers.Provider, timeout time.Duration) (config.Config, error) {
	if provider, err := selectProvider(providers, timeout); err == nil {
		return provider.FetchConfig()
	} else {
		return config.Config{}, err
	}
}

func selectProvider(ps []providers.Provider, timeout time.Duration) (providers.Provider, error) {
	online := make(chan providers.Provider)
	wg := sync.WaitGroup{}
	stop := make(chan struct{})
	defer close(stop)

	for _, p := range ps {
		wg.Add(1)
		go func(provider providers.Provider) {
			defer wg.Done()

			for {
				if provider.IsOnline() {
					online <- provider
					return
				} else if !provider.ShouldRetry() {
					return
				}

				select {
				case <-time.After(provider.BackoffDuration()):
				case <-stop:
					return
				}
			}
		}(p)
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	var provider providers.Provider
	select {
	case provider = <-online:
		return provider, nil
	case <-done:
		return nil, ErrNoProviders
	case <-time.After(timeout):
		return nil, ErrTimeout
	}
}
<commit_msg>exec/engine: add comments<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// 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 exec

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"sort"
	"sync"
	"time"

	"github.com/coreos/ignition/config"
	"github.com/coreos/ignition/src/exec/stages"
	"github.com/coreos/ignition/src/log"
	"github.com/coreos/ignition/src/providers"
)

const (
	DefaultFetchTimeout = time.Minute
)

var (
	ErrNoProviders = errors.New("no config providers were online")
	ErrTimeout     = errors.New("timed out while waiting for a config provider to come online")
)

// Engine represents the entity that fetches and executes a configuration.
type Engine struct {
	ConfigCache  string
	FetchTimeout time.Duration
	Logger       log.Logger
	Root         string
	providers    map[string]providers.Provider
}

// AddProvider registers a configuration provider with the engine.
func (e *Engine) AddProvider(provider providers.Provider) {
	if e.providers == nil {
		e.providers = map[string]providers.Provider{}
	}
	e.providers[provider.Name()] = provider
}

// Providers returns a list of the registered providers in alphabetical order.
func (e Engine) Providers() []providers.Provider {
	keys := []string{}
	for key := range e.providers {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	providers := make([]providers.Provider, 0, len(e.providers))
	for _, key := range keys {
		providers = append(providers, e.providers[key])
	}
	return providers
}

// Run executes the stage of the given name. It returns true if the stage
// successfully ran and false if there were any errors.
func (e Engine) Run(stageName string) bool {
	config, err := e.acquireConfig()
	if err != nil {
		e.Logger.Crit("failed to acquire config: %v", err)
		return false
	}

	e.Logger.PushPrefix(stageName)
	defer e.Logger.PopPrefix()
	return stages.Get(stageName).Create(&e.Logger, e.Root).Run(config)
}

// acquireConfig returns the configuration, first checking a local cache
// before attempting to fetch it from the registered providers.
func (e Engine) acquireConfig() (cfg config.Config, err error) {
	// First try read the config @ e.ConfigCache.
	b, err := ioutil.ReadFile(e.ConfigCache)
	if err == nil {
		if err = json.Unmarshal(b, &cfg); err != nil {
			e.Logger.Crit("failed to parse cached config: %v", err)
		}
		return
	}

	// (Re)Fetch the config if the cache is unreadable.
	cfg, err = fetchConfig(e.Providers(), e.FetchTimeout)
	if err != nil {
		e.Logger.Crit("failed to fetch config: %v", err)
		return
	}
	e.Logger.Debug("fetched config: %+v", cfg)

	// Populate the config cache.
	b, err = json.Marshal(cfg)
	if err != nil {
		e.Logger.Crit("failed to marshal cached config: %v", err)
		return
	}
	if err = ioutil.WriteFile(e.ConfigCache, b, 0640); err != nil {
		e.Logger.Crit("failed to write cached config: %v", err)
		return
	}

	return
}

// fetchConfig returns the configuration from the first available provider or
// returns an error if none of the providers are available.
func fetchConfig(providers []providers.Provider, timeout time.Duration) (config.Config, error) {
	if provider, err := selectProvider(providers, timeout); err == nil {
		return provider.FetchConfig()
	} else {
		return config.Config{}, err
	}
}

// selectProvider chooses the first online provider, given a list of providers
// and a timeout. If none of the providers will ever be online, or if the
// timeout elapses before any providers are online, this returns an appropriate
// error.
func selectProvider(ps []providers.Provider, timeout time.Duration) (providers.Provider, error) {
	online := make(chan providers.Provider)
	wg := sync.WaitGroup{}
	stop := make(chan struct{})
	defer close(stop)

	for _, p := range ps {
		wg.Add(1)
		go func(provider providers.Provider) {
			defer wg.Done()

			for {
				if provider.IsOnline() {
					online <- provider
					return
				} else if !provider.ShouldRetry() {
					return
				}

				select {
				case <-time.After(provider.BackoffDuration()):
				case <-stop:
					return
				}
			}
		}(p)
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	var provider providers.Provider
	select {
	case provider = <-online:
		return provider, nil
	case <-done:
		return nil, ErrNoProviders
	case <-time.After(timeout):
		return nil, ErrTimeout
	}
}
<|endoftext|>


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

package query_test

import (
	"context"
	"fmt"
	"log"
	"reflect"
	"strings"
	"testing"

	"go-hep.org/x/hep/xrootd"
	"go-hep.org/x/hep/xrootd/internal/xrdenc"
	"go-hep.org/x/hep/xrootd/xrdfs"
	"go-hep.org/x/hep/xrootd/xrdproto"
	"go-hep.org/x/hep/xrootd/xrdproto/query"
)

func TestRequest(t *testing.T) {
	for _, want := range []query.Request{
		{
			Query: query.Config,
			Args:  []byte("bind_max chksum"),
		},
		{
			Query:  query.Visa,
			Handle: [4]byte{1, 2, 3, 4},
		},
	} {
		t.Run("", func(t *testing.T) {
			var (
				err error
				w   = new(xrdenc.WBuffer)
				got query.Request
			)

			if want.ReqID() != query.RequestID {
				t.Fatalf("invalid request ID: got=%d want=%d", want.ReqID(), query.RequestID)
			}

			if want.ShouldSign() {
				t.Fatalf("invalid")
			}

			err = want.MarshalXrd(w)
			if err != nil {
				t.Fatalf("could not marshal request: %v", err)
			}

			r := xrdenc.NewRBuffer(w.Bytes())
			err = got.UnmarshalXrd(r)
			if err != nil {
				t.Fatalf("could not unmarshal request: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("round trip failed:\ngot = %#v\nwant= %#v\n", got, want)
			}
		})
	}
}

func TestResponse(t *testing.T) {
	for _, want := range []query.Response{
		{
			Data: []byte("1234"),
		},
		{
			Data: []byte("oss.cgroup=public&oss.space=499337216&oss.free=444280832&oss.maxf=444280832&oss.used=55056384&oss.quota=-1\x00"),
		},
	} {
		t.Run("", func(t *testing.T) {
			var (
				err error
				w   = new(xrdenc.WBuffer)
				got query.Response
			)

			if want.RespID() != query.RequestID {
				t.Fatalf("invalid response ID: got=%d want=%d", want.RespID(), query.RequestID)
			}

			err = want.MarshalXrd(w)
			if err != nil {
				t.Fatalf("could not marshal response: %v", err)
			}

			r := xrdenc.NewRBuffer(w.Bytes())
			err = got.UnmarshalXrd(r)
			if err != nil {
				t.Fatalf("could not unmarshal response: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("round trip failed:\ngot = %#v\nwant= %#v\n", got, want)
			}
		})
	}
}

func Example() {
	bkg := context.Background()

	cl, err := xrootd.NewClient(bkg, "ccxrootdgotest.in2p3.fr:9001", "gopher")
	if err != nil {
		log.Fatalf("could not create client: %v", err)
	}
	defer cl.Close()

	fs := cl.FS()
	f, err := fs.Open(bkg, "/tmp/dir1/file1.txt", xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
	if err != nil {
		log.Fatalf("open error: %v", err)
	}
	defer f.Close(bkg)

	var (
		resp query.Response
		req  = query.Request{
			Query:  query.Space,
			Handle: f.Handle(),
			Args:   []byte("/tmp/dir1/file1.txt"),
		}
	)

	id, err := cl.Send(bkg, &resp, &req)
	if err != nil {
		log.Fatalf("space request error: %v", err)
	}
	fmt.Printf("sess: %s\n", id)
	// fmt.Printf("space: %q\n", resp.Data)

	cfg := []string{
		"bind_max",
		"chksum",
		"cid", "cms", "pio_max",
		"readv_ior_max",
		"readv_iov_max",
		"role",
		"sitename",
		"tpc",
		"version",
		"wan_port",
		"wan_window",
		"window",
	}

	req = query.Request{
		Query: query.Config,
		Args:  []byte(strings.Join(cfg, " ")),
	}

	id, err = cl.Send(bkg, &resp, &req)
	if err != nil {
		log.Fatalf("config request error: %v", err)
	}
	for i, v := range strings.Split(strings.TrimRight(string(resp.Data), "\n"), "\n") {
		if v == cfg[i] {
			fmt.Printf("config: %s=N/A\n", v)
			continue
		}
		fmt.Printf("config: %s=%q\n", cfg[i], v)
	}

	// Output:
	// sess: ccxrootdgotest.in2p3.fr:9001
	// config: bind_max="15"
	// config: chksum=N/A
	// config: cid=N/A
	// config: cms="none|"
	// config: pio_max="5"
	// config: readv_ior_max="2097136"
	// config: readv_iov_max="1024"
	// config: role="server"
	// config: sitename=N/A
	// config: tpc=N/A
	// config: version="v4.8.4"
	// config: wan_port=N/A
	// config: wan_window=N/A
	// config: window="87380"
}

var (
	_ xrdproto.Request  = (*query.Request)(nil)
	_ xrdproto.Response = (*query.Response)(nil)
)
<commit_msg>xrootd/xrdproto/query: update example for new XRootD version<commit_after>// Copyright 2018 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package query_test

import (
	"context"
	"fmt"
	"log"
	"reflect"
	"strings"
	"testing"

	"go-hep.org/x/hep/xrootd"
	"go-hep.org/x/hep/xrootd/internal/xrdenc"
	"go-hep.org/x/hep/xrootd/xrdfs"
	"go-hep.org/x/hep/xrootd/xrdproto"
	"go-hep.org/x/hep/xrootd/xrdproto/query"
)

func TestRequest(t *testing.T) {
	for _, want := range []query.Request{
		{
			Query: query.Config,
			Args:  []byte("bind_max chksum"),
		},
		{
			Query:  query.Visa,
			Handle: [4]byte{1, 2, 3, 4},
		},
	} {
		t.Run("", func(t *testing.T) {
			var (
				err error
				w   = new(xrdenc.WBuffer)
				got query.Request
			)

			if want.ReqID() != query.RequestID {
				t.Fatalf("invalid request ID: got=%d want=%d", want.ReqID(), query.RequestID)
			}

			if want.ShouldSign() {
				t.Fatalf("invalid")
			}

			err = want.MarshalXrd(w)
			if err != nil {
				t.Fatalf("could not marshal request: %v", err)
			}

			r := xrdenc.NewRBuffer(w.Bytes())
			err = got.UnmarshalXrd(r)
			if err != nil {
				t.Fatalf("could not unmarshal request: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("round trip failed:\ngot = %#v\nwant= %#v\n", got, want)
			}
		})
	}
}

func TestResponse(t *testing.T) {
	for _, want := range []query.Response{
		{
			Data: []byte("1234"),
		},
		{
			Data: []byte("oss.cgroup=public&oss.space=499337216&oss.free=444280832&oss.maxf=444280832&oss.used=55056384&oss.quota=-1\x00"),
		},
	} {
		t.Run("", func(t *testing.T) {
			var (
				err error
				w   = new(xrdenc.WBuffer)
				got query.Response
			)

			if want.RespID() != query.RequestID {
				t.Fatalf("invalid response ID: got=%d want=%d", want.RespID(), query.RequestID)
			}

			err = want.MarshalXrd(w)
			if err != nil {
				t.Fatalf("could not marshal response: %v", err)
			}

			r := xrdenc.NewRBuffer(w.Bytes())
			err = got.UnmarshalXrd(r)
			if err != nil {
				t.Fatalf("could not unmarshal response: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("round trip failed:\ngot = %#v\nwant= %#v\n", got, want)
			}
		})
	}
}

func Example() {
	bkg := context.Background()

	cl, err := xrootd.NewClient(bkg, "ccxrootdgotest.in2p3.fr:9001", "gopher")
	if err != nil {
		log.Fatalf("could not create client: %v", err)
	}
	defer cl.Close()

	fs := cl.FS()
	f, err := fs.Open(bkg, "/tmp/dir1/file1.txt", xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
	if err != nil {
		log.Fatalf("open error: %v", err)
	}
	defer f.Close(bkg)

	var (
		resp query.Response
		req  = query.Request{
			Query:  query.Space,
			Handle: f.Handle(),
			Args:   []byte("/tmp/dir1/file1.txt"),
		}
	)

	id, err := cl.Send(bkg, &resp, &req)
	if err != nil {
		log.Fatalf("space request error: %v", err)
	}
	fmt.Printf("sess: %s\n", id)
	// fmt.Printf("space: %q\n", resp.Data)

	cfg := []string{
		"bind_max",
		"chksum",
		"cid", "cms", "pio_max",
		"readv_ior_max",
		"readv_iov_max",
		"role",
		"sitename",
		"tpc",
		"version",
		"wan_port",
		"wan_window",
		"window",
	}

	req = query.Request{
		Query: query.Config,
		Args:  []byte(strings.Join(cfg, " ")),
	}

	id, err = cl.Send(bkg, &resp, &req)
	if err != nil {
		log.Fatalf("config request error: %v", err)
	}
	for i, v := range strings.Split(strings.TrimRight(string(resp.Data), "\n"), "\n") {
		if v == cfg[i] {
			fmt.Printf("config: %s=N/A\n", v)
			continue
		}
		fmt.Printf("config: %s=%q\n", cfg[i], v)
	}

	// Output:
	// sess: ccxrootdgotest.in2p3.fr:9001
	// config: bind_max="15"
	// config: chksum=N/A
	// config: cid=N/A
	// config: cms="none|"
	// config: pio_max="5"
	// config: readv_ior_max="2097136"
	// config: readv_iov_max="1024"
	// config: role="server"
	// config: sitename=N/A
	// config: tpc=N/A
	// config: version="v4.8.5"
	// config: wan_port=N/A
	// config: wan_window=N/A
	// config: window="87380"
}

var (
	_ xrdproto.Request  = (*query.Request)(nil)
	_ xrdproto.Response = (*query.Response)(nil)
)
<|endoftext|>


<commit_before>// Adds a single commit to a repo with the current data, e.g. 2022-04-01.
//
// Used in a kubernetes cron job to populate a repo that has one commit per day.
package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/git"
	"go.skia.org/infra/go/gitauth"
	"go.skia.org/infra/go/now"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
)

const filename = "DATE"

var (
	authorEmail = flag.String("author_email", "perf-comp-ui@skia-public.iam.gserviceaccount.com", "Email address of the git author.")
	local       = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	repoURL     = flag.String("gitrepo", "https://skia.googlesource.com/perf-compui", "The repo that has commits associated with runs of the cron job.")
)

func main() {
	ctx := context.Background()
	ts, err := auth.NewDefaultTokenSource(*local, auth.ScopeGerrit)
	if err != nil {
		sklog.Fatal(err)
	}

	// Do all the work in a temp directory.
	workDir, err := os.MkdirTemp("", "comp-ui-cron-job")
	if err != nil {
		sklog.Fatal(err)
	}
	defer func() {
		if err := os.RemoveAll(workDir); err != nil {
			sklog.Error(err)
		}
	}()

	// We presume that if running locally that you've already authenticated to
	// Gerrit, otherwise write out a git cookie that enables R/W access to the
	// git repo.
	if !*local {
		if _, err := gitauth.New(ts, "/tmp/git-cookie", true, *authorEmail); err != nil {
			sklog.Fatal(err)
		}
	}

	// Checkout the repo.
	checkout, err := git.NewCheckout(ctx, *repoURL, workDir)
	if err != nil {
		sklog.Fatalf("Unable to create the checkout of %q at %q: %s", *repoURL, workDir, err)
	}
	if err := checkout.UpdateBranch(ctx, git.MainBranch); err != nil {
		sklog.Fatalf("Unable to update the checkout of %q at %q: %s", *repoURL, workDir, err)
	}

	// We write today as the data into a file called "DATE".
	timestamp := now.Now(ctx)
	dateAsString := timestamp.Format("2006.01.02")
	err = util.WithWriteFile(filepath.Join(checkout.Dir(), filename), func(w io.Writer) error {
		_, err := w.Write([]byte(dateAsString))
		return err
	})
	if err != nil {
		sklog.Fatal(err)
	}

	// Commit and push the update to the git repo.
	if msg, err := checkout.Git(ctx, "add", filename); err != nil {
		sklog.Fatal(err, msg)
	}
	if msg, err := checkout.Git(ctx, "commit", "-m", dateAsString, fmt.Sprintf("--date=%d", timestamp.Unix())); err != nil {
		sklog.Fatal(err, msg)
	}
	if msg, err := checkout.Git(ctx, "push", git.DefaultRemote, git.MainBranch); err != nil {
		sklog.Fatal(err, msg)
	}
}
<commit_msg>[comp-ui] Add success logging to gitcron.<commit_after>// Adds a single commit to a repo with the current data, e.g. 2022-04-01.
//
// Used in a kubernetes cron job to populate a repo that has one commit per day.
package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/git"
	"go.skia.org/infra/go/gitauth"
	"go.skia.org/infra/go/now"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
)

const filename = "DATE"

var (
	authorEmail = flag.String("author_email", "perf-comp-ui@skia-public.iam.gserviceaccount.com", "Email address of the git author.")
	local       = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	repoURL     = flag.String("gitrepo", "https://skia.googlesource.com/perf-compui", "The repo that has commits associated with runs of the cron job.")
)

func main() {
	ctx := context.Background()
	ts, err := auth.NewDefaultTokenSource(*local, auth.ScopeGerrit)
	if err != nil {
		sklog.Fatal(err)
	}

	// Do all the work in a temp directory.
	workDir, err := os.MkdirTemp("", "comp-ui-cron-job")
	if err != nil {
		sklog.Fatal(err)
	}
	defer func() {
		if err := os.RemoveAll(workDir); err != nil {
			sklog.Error(err)
		}
	}()

	// We presume that if running locally that you've already authenticated to
	// Gerrit, otherwise write out a git cookie that enables R/W access to the
	// git repo.
	if !*local {
		if _, err := gitauth.New(ts, "/tmp/git-cookie", true, *authorEmail); err != nil {
			sklog.Fatal(err)
		}
	}

	// Checkout the repo.
	checkout, err := git.NewCheckout(ctx, *repoURL, workDir)
	if err != nil {
		sklog.Fatalf("Unable to create the checkout of %q at %q: %s", *repoURL, workDir, err)
	}
	if err := checkout.UpdateBranch(ctx, git.MainBranch); err != nil {
		sklog.Fatalf("Unable to update the checkout of %q at %q: %s", *repoURL, workDir, err)
	}

	// We write today as the data into a file called "DATE".
	timestamp := now.Now(ctx)
	dateAsString := timestamp.Format("2006.01.02")
	err = util.WithWriteFile(filepath.Join(checkout.Dir(), filename), func(w io.Writer) error {
		_, err := w.Write([]byte(dateAsString))
		return err
	})
	if err != nil {
		sklog.Fatal(err)
	}

	// Commit and push the update to the git repo.
	if msg, err := checkout.Git(ctx, "add", filename); err != nil {
		sklog.Fatal(err, msg)
	}
	if msg, err := checkout.Git(ctx, "commit", "-m", dateAsString, fmt.Sprintf("--date=%d", timestamp.Unix())); err != nil {
		sklog.Fatal(err, msg)
	}
	if msg, err := checkout.Git(ctx, "push", git.DefaultRemote, git.MainBranch); err != nil {
		sklog.Fatal(err, msg)
	}
	sklog.Infof("Success: %s", dateAsString)
}
<|endoftext|>


<commit_before>// Package us020 allows interfacing with the US020 ultrasonic range finder.
package us020

import (
	"sync"
	"time"

	"github.com/kid0m4n/go-rpi/sensor/bmp085"
	"github.com/stianeikeland/go-rpio"
)

const (
	pulseDelay = 30000 * time.Nanosecond
)

// A US020 implements access to a US020 ultrasonic range finder.
type US020 interface {
	// Distance computes the distance of the bot from the closest obstruction.
	Distance() (float64, error)
}

type us020 struct {
	echoPinNumber, triggerPinNumber int

	echoPin    rpio.Pin
	triggerPin rpio.Pin

	speedSound float64

	initialized bool
	mu          *sync.RWMutex
}

// New creates a new US020 interface. The bus variable controls
// the I2C bus used to communicate with the device.
func New(e, t int) US020 {
	return &us020{echoPinNumber: e, triggerPinNumber: t, mu: new(sync.RWMutex)}
}

func (d *us020) setup() (err error) {
	d.mu.RLock()
	if d.initialized {
		d.mu.RUnlock()
		return
	}
	d.mu.RUnlock()

	d.mu.Lock()
	defer d.mu.Unlock()

	if err = rpio.Open(); err != nil {
		return
	}

	d.echoPin = rpio.Pin(d.echoPinNumber)       // ECHO port on the US020
	d.triggerPin = rpio.Pin(d.triggerPinNumber) // TRIGGER port on the US020

	temp, err := bmp085.Temperature()
	if err != nil {
		d.speedSound = 340
	} else {
		d.speedSound = 331.4 + 0.606*temp
	}

	d.initialized = true

	return nil
}

// Distance computes the distance of the bot from the closest obstruction.
func (d *us020) Distance() (distance float64, err error) {
	if err = d.setup(); err != nil {
		return
	}

	// Generate a TRIGGER pulse
	d.triggerPin.High()
	time.Sleep(pulseDelay)
	d.triggerPin.Low()

	// Wait until ECHO goes high
	for d.echoPin.Read() == rpio.Low {
	}

	startTime := time.Now() // Record time when ECHO goes high

	// Wait until ECHO goes low
	for d.echoPin.Read() == rpio.High {
	}

	duration := time.Since(startTime) // Calculate time lapsed for ECHO to transition from high to low

	// Calculate the distance based on the time computed
	distance = float64(duration.Nanoseconds()) / 10000000 * (d.speedSound / 2)

	return
}
<commit_msg>set the pin modes appropriately<commit_after>// Package us020 allows interfacing with the US020 ultrasonic range finder.
package us020

import (
	"log"
	"sync"
	"time"

	"github.com/kid0m4n/go-rpi/sensor/bmp085"
	"github.com/stianeikeland/go-rpio"
)

const (
	pulseDelay = 30000 * time.Nanosecond
)

// A US020 implements access to a US020 ultrasonic range finder.
type US020 interface {
	// Distance computes the distance of the bot from the closest obstruction.
	Distance() (float64, error)
}

type us020 struct {
	echoPinNumber, triggerPinNumber int

	echoPin    rpio.Pin
	triggerPin rpio.Pin

	speedSound float64

	initialized bool
	mu          *sync.RWMutex

	debug bool
}

// New creates a new US020 interface. The bus variable controls
// the I2C bus used to communicate with the device.
func New(e, t int) US020 {
	return &us020{echoPinNumber: e, triggerPinNumber: t, mu: new(sync.RWMutex)}
}

func (d *us020) setup() (err error) {
	d.mu.RLock()
	if d.initialized {
		d.mu.RUnlock()
		return
	}
	d.mu.RUnlock()

	d.mu.Lock()
	defer d.mu.Unlock()

	if err = rpio.Open(); err != nil {
		return
	}

	d.echoPin = rpio.Pin(d.echoPinNumber)       // ECHO port on the US020
	d.triggerPin = rpio.Pin(d.triggerPinNumber) // TRIGGER port on the US020

	d.echoPin.Input()
	d.triggerPin.Output()

	temp, err := bmp085.Temperature()
	if err != nil {
		d.speedSound = 340
	} else {
		d.speedSound = 331.4 + 0.606*temp

		if d.debug {
			log.Printf("read a temperature of %v, so speed of sound = %v", temp, d.speedSound)
		}
	}

	d.initialized = true

	return nil
}

// Distance computes the distance of the bot from the closest obstruction.
func (d *us020) Distance() (distance float64, err error) {
	if err = d.setup(); err != nil {
		return
	}

	if d.debug {
		log.Print("trigetting pulse")
	}

	// Generate a TRIGGER pulse
	d.triggerPin.High()
	time.Sleep(pulseDelay)
	d.triggerPin.Low()

	if d.debug {
		log.Print("waiting for echo to go high")
	}

	// Wait until ECHO goes high
	for d.echoPin.Read() == rpio.Low {
	}

	startTime := time.Now() // Record time when ECHO goes high

	if d.debug {
		log.Print("waiting for echo to go low")
	}

	// Wait until ECHO goes low
	for d.echoPin.Read() == rpio.High {
	}

	duration := time.Since(startTime) // Calculate time lapsed for ECHO to transition from high to low

	// Calculate the distance based on the time computed
	distance = float64(duration.Nanoseconds()) / 10000000 * (d.speedSound / 2)

	return
}
<|endoftext|>