<commit_before>package v7_test

import (
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"

	"code.cloudfoundry.org/cli/actor/actionerror"
	"code.cloudfoundry.org/cli/actor/v7action"
	"code.cloudfoundry.org/cli/command/commandfakes"
	"code.cloudfoundry.org/cli/command/translatableerror"
	. "code.cloudfoundry.org/cli/command/v7"
	"code.cloudfoundry.org/cli/command/v7/v7fakes"
	"code.cloudfoundry.org/cli/util/configv3"
	"code.cloudfoundry.org/cli/util/ui"

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

var _ = Describe("download-droplet Command", func() {

	var (
		cmd             DownloadDropletCommand
		testUI          *ui.UI
		fakeConfig      *commandfakes.FakeConfig
		fakeSharedActor *commandfakes.FakeSharedActor
		fakeActor       *v7fakes.FakeActor
		binaryName      string
		executeErr      error
	)

	BeforeEach(func() {
		testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer())
		fakeConfig = new(commandfakes.FakeConfig)
		fakeSharedActor = new(commandfakes.FakeSharedActor)
		fakeActor = new(v7fakes.FakeActor)

		cmd = DownloadDropletCommand{
			BaseCommand: BaseCommand{
				UI:          testUI,
				Config:      fakeConfig,
				SharedActor: fakeSharedActor,
				Actor:       fakeActor,
			},
		}

		cmd.RequiredArgs.AppName = "some-app"

		binaryName = "faceman"
		fakeConfig.BinaryNameReturns(binaryName)
		fakeConfig.HasTargetedOrganizationReturns(true)
		fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"})
		fakeConfig.HasTargetedSpaceReturns(true)
		fakeConfig.TargetedSpaceReturns(configv3.Space{
			GUID: "some-space-guid",
			Name: "some-space"})
		fakeConfig.CurrentUserReturns(
			configv3.User{Name: "some-user"},
			nil)
	})

	JustBeforeEach(func() {
		executeErr = cmd.Execute(nil)
	})

	When("checking target fails", func() {
		BeforeEach(func() {
			fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: binaryName})
		})

		It("returns an error if the check fails", func() {
			Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: "faceman"}))

			Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1))
			checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0)
			Expect(checkTargetedOrg).To(BeTrue())
			Expect(checkTargetedSpace).To(BeTrue())
		})
	})

	When("downloading the droplet succeeds", func() {
		var (
			pathToDropletFile string
			dropletGUID       string
		)

		BeforeEach(func() {
			dropletGUID = fmt.Sprintf("fake-droplet-guid-%d", rand.Uint64())
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet-bytes"), dropletGUID, v7action.Warnings{"some-warning"}, nil)

			currentDir, _ := os.Getwd()
			pathToDropletFile = filepath.Join(currentDir, fmt.Sprintf("droplet_%s.tgz", dropletGUID))
		})

		AfterEach(func() {
			Expect(os.Remove(pathToDropletFile)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball in the current directory", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			Expect(fakeActor.DownloadCurrentDropletByAppNameCallCount()).To(Equal(1))
			appArg, spaceGUIDArg := fakeActor.DownloadCurrentDropletByAppNameArgsForCall(0)
			Expect(appArg).To(Equal("some-app"))
			Expect(spaceGUIDArg).To(Equal("some-space-guid"))

			fileContents, err := ioutil.ReadFile(pathToDropletFile)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet-bytes"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at .*droplet_%s.tgz`, dropletGUID))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("the droplet guid is passed in", func() {
		var (
			dropletGUID       string
			pathToDropletFile string
		)

		BeforeEach(func() {
			dropletGUID = fmt.Sprintf("fake-droplet-guid-%d", rand.Uint64())
			pathToDropletFile = fmt.Sprintf("droplet_%s.tgz", dropletGUID)

			setFlag(&cmd, "--droplet", dropletGUID)

			fakeActor.DownloadDropletByGUIDAndAppNameReturns([]byte("some-droplet-bytes"), v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.Remove(pathToDropletFile)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball in the current directory", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			Expect(fakeActor.DownloadDropletByGUIDAndAppNameCallCount()).To(Equal(1))
			dropletGUIDArg, appArg, spaceGUIDArg := fakeActor.DownloadDropletByGUIDAndAppNameArgsForCall(0)
			Expect(dropletGUIDArg).To(Equal(dropletGUID))
			Expect(appArg).To(Equal("some-app"))
			Expect(spaceGUIDArg).To(Equal("some-space-guid"))

			fileContents, err := ioutil.ReadFile(pathToDropletFile)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet-bytes"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading droplet %s for app some-app in org some-org / space some-space as some-user...", dropletGUID))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at .*droplet_%s.tgz`, dropletGUID))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("a path to a file is passed in", func() {
		var filePath string
		BeforeEach(func() {
			filePath = fmt.Sprintf("fake-file-%d", rand.Uint64())

			setFlag(&cmd, "--path", filePath)
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.Remove(filePath)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball at the specified path", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			fileContents, err := ioutil.ReadFile(filePath)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at %s`, filePath))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("a path to an existing directory is passed in", func() {
		var tmpDir string

		BeforeEach(func() {
			var err error
			tmpDir, err = ioutil.TempDir("", "droplets")
			Expect(err).NotTo(HaveOccurred())

			setFlag(&cmd, "--path", tmpDir)

			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.RemoveAll(tmpDir)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball at the specified path", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			fileContents, err := ioutil.ReadFile(filepath.Join(tmpDir, "droplet_some-droplet-guid.tgz"))
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(executeErr).ToNot(HaveOccurred())
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			pathRegExp := regexp.QuoteMeta(filepath.Join(tmpDir, "droplet_some-droplet-guid.tgz"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at %s`, pathRegExp))
			Expect(testUI.Out).To(Say("OK"))
		})
	})

	When("a path to a file in an invalid directory is passed in", func() {
		BeforeEach(func() {
			cmd.Path = "not/exist/some-file.tgz"
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		It("returns an appropriate error", func() {
			_, ok := executeErr.(translatableerror.DropletFileError)
			Expect(ok).To(BeTrue())
		})
	})

	When("there is an error downloading the droplet", func() {
		BeforeEach(func() {
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte{}, "", v7action.Warnings{"some-warning"}, errors.New("something went wrong"))
		})

		It("displays warnings and returns an error", func() {
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(executeErr).To(MatchError("something went wrong"))
		})
	})

	When("the app does not have a current droplet", func() {
		BeforeEach(func() {
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte{}, "", v7action.Warnings{"some-warning"}, actionerror.DropletNotFoundError{})
		})

		It("displays warnings and returns an error", func() {
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(executeErr).To(MatchError(translatableerror.NoDropletForAppError{AppName: "some-app"}))
		})
	})
})
<commit_msg>test: fix parallel flake in droplet download<commit_after>package v7_test

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"

	"code.cloudfoundry.org/cli/actor/actionerror"
	"code.cloudfoundry.org/cli/actor/v7action"
	"code.cloudfoundry.org/cli/command/commandfakes"
	"code.cloudfoundry.org/cli/command/translatableerror"
	. "code.cloudfoundry.org/cli/command/v7"
	"code.cloudfoundry.org/cli/command/v7/v7fakes"
	"code.cloudfoundry.org/cli/util/configv3"
	"code.cloudfoundry.org/cli/util/ui"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
)

var _ = Describe("download-droplet Command", func() {

	var (
		cmd             DownloadDropletCommand
		testUI          *ui.UI
		fakeConfig      *commandfakes.FakeConfig
		fakeSharedActor *commandfakes.FakeSharedActor
		fakeActor       *v7fakes.FakeActor
		binaryName      string
		executeErr      error
	)

	BeforeEach(func() {
		testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer())
		fakeConfig = new(commandfakes.FakeConfig)
		fakeSharedActor = new(commandfakes.FakeSharedActor)
		fakeActor = new(v7fakes.FakeActor)

		cmd = DownloadDropletCommand{
			BaseCommand: BaseCommand{
				UI:          testUI,
				Config:      fakeConfig,
				SharedActor: fakeSharedActor,
				Actor:       fakeActor,
			},
		}

		cmd.RequiredArgs.AppName = "some-app"

		binaryName = "faceman"
		fakeConfig.BinaryNameReturns(binaryName)
		fakeConfig.HasTargetedOrganizationReturns(true)
		fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"})
		fakeConfig.HasTargetedSpaceReturns(true)
		fakeConfig.TargetedSpaceReturns(configv3.Space{
			GUID: "some-space-guid",
			Name: "some-space"})
		fakeConfig.CurrentUserReturns(
			configv3.User{Name: "some-user"},
			nil)
	})

	JustBeforeEach(func() {
		executeErr = cmd.Execute(nil)
	})

	When("checking target fails", func() {
		BeforeEach(func() {
			fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: binaryName})
		})

		It("returns an error if the check fails", func() {
			Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: "faceman"}))

			Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1))
			checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0)
			Expect(checkTargetedOrg).To(BeTrue())
			Expect(checkTargetedSpace).To(BeTrue())
		})
	})

	When("downloading the droplet succeeds", func() {
		var (
			pathToDropletFile string
			dropletGUID       string
		)

		BeforeEach(func() {
			dropletGUID = RandomString("fake-droplet-guid")
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet-bytes"), dropletGUID, v7action.Warnings{"some-warning"}, nil)

			currentDir, _ := os.Getwd()
			pathToDropletFile = filepath.Join(currentDir, fmt.Sprintf("droplet_%s.tgz", dropletGUID))
		})

		AfterEach(func() {
			Expect(os.Remove(pathToDropletFile)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball in the current directory", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			Expect(fakeActor.DownloadCurrentDropletByAppNameCallCount()).To(Equal(1))
			appArg, spaceGUIDArg := fakeActor.DownloadCurrentDropletByAppNameArgsForCall(0)
			Expect(appArg).To(Equal("some-app"))
			Expect(spaceGUIDArg).To(Equal("some-space-guid"))

			fileContents, err := ioutil.ReadFile(pathToDropletFile)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet-bytes"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at .*droplet_%s.tgz`, dropletGUID))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("the droplet guid is passed in", func() {
		var (
			dropletGUID       string
			pathToDropletFile string
		)

		BeforeEach(func() {
			dropletGUID = RandomString("fake-droplet-guid")
			pathToDropletFile = fmt.Sprintf("droplet_%s.tgz", dropletGUID)

			setFlag(&cmd, "--droplet", dropletGUID)

			fakeActor.DownloadDropletByGUIDAndAppNameReturns([]byte("some-droplet-bytes"), v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.Remove(pathToDropletFile)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball in the current directory", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			Expect(fakeActor.DownloadDropletByGUIDAndAppNameCallCount()).To(Equal(1))
			dropletGUIDArg, appArg, spaceGUIDArg := fakeActor.DownloadDropletByGUIDAndAppNameArgsForCall(0)
			Expect(dropletGUIDArg).To(Equal(dropletGUID))
			Expect(appArg).To(Equal("some-app"))
			Expect(spaceGUIDArg).To(Equal("some-space-guid"))

			fileContents, err := ioutil.ReadFile(pathToDropletFile)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet-bytes"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading droplet %s for app some-app in org some-org / space some-space as some-user...", dropletGUID))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at .*droplet_%s.tgz`, dropletGUID))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("a path to a file is passed in", func() {
		var filePath string
		BeforeEach(func() {
			filePath = RandomString("fake-file")

			setFlag(&cmd, "--path", filePath)
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.Remove(filePath)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball at the specified path", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			fileContents, err := ioutil.ReadFile(filePath)
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at %s`, filePath))
			Expect(testUI.Out).To(Say("OK"))
			Expect(executeErr).ToNot(HaveOccurred())
		})
	})

	When("a path to an existing directory is passed in", func() {
		var tmpDir string

		BeforeEach(func() {
			var err error
			tmpDir, err = ioutil.TempDir("", "droplets")
			Expect(err).NotTo(HaveOccurred())

			setFlag(&cmd, "--path", tmpDir)

			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		AfterEach(func() {
			Expect(os.RemoveAll(tmpDir)).ToNot(HaveOccurred())
		})

		It("creates a droplet tarball at the specified path", func() {
			Expect(executeErr).ToNot(HaveOccurred())

			fileContents, err := ioutil.ReadFile(filepath.Join(tmpDir, "droplet_some-droplet-guid.tgz"))
			Expect(err).ToNot(HaveOccurred())
			Expect(string(fileContents)).To(Equal("some-droplet"))
		})

		It("displays the file it created and returns no errors", func() {
			Expect(executeErr).ToNot(HaveOccurred())
			Expect(testUI.Out).To(Say("Downloading current droplet for app some-app in org some-org / space some-space as some-user..."))
			Expect(testUI.Err).To(Say("some-warning"))
			pathRegExp := regexp.QuoteMeta(filepath.Join(tmpDir, "droplet_some-droplet-guid.tgz"))
			Expect(testUI.Out).To(Say(`Droplet downloaded successfully at %s`, pathRegExp))
			Expect(testUI.Out).To(Say("OK"))
		})
	})

	When("a path to a file in an invalid directory is passed in", func() {
		BeforeEach(func() {
			cmd.Path = "not/exist/some-file.tgz"
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte("some-droplet"), "some-droplet-guid", v7action.Warnings{"some-warning"}, nil)
		})

		It("returns an appropriate error", func() {
			_, ok := executeErr.(translatableerror.DropletFileError)
			Expect(ok).To(BeTrue())
		})
	})

	When("there is an error downloading the droplet", func() {
		BeforeEach(func() {
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte{}, "", v7action.Warnings{"some-warning"}, errors.New("something went wrong"))
		})

		It("displays warnings and returns an error", func() {
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(executeErr).To(MatchError("something went wrong"))
		})
	})

	When("the app does not have a current droplet", func() {
		BeforeEach(func() {
			fakeActor.DownloadCurrentDropletByAppNameReturns([]byte{}, "", v7action.Warnings{"some-warning"}, actionerror.DropletNotFoundError{})
		})

		It("displays warnings and returns an error", func() {
			Expect(testUI.Err).To(Say("some-warning"))
			Expect(executeErr).To(MatchError(translatableerror.NoDropletForAppError{AppName: "some-app"}))
		})
	})
})
<|endoftext|>


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

package deployer

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"

	"github.com/juju/errors"
	"github.com/juju/utils/arch"
	"github.com/juju/utils/series"
	"github.com/juju/utils/shell"
	"github.com/juju/version"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/agent/tools"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/service"
	"github.com/juju/juju/service/common"
	jujuversion "github.com/juju/juju/version"
)

// TODO(ericsnow) Use errors.Trace, etc. in this file.

// APICalls defines the interface to the API that the simple context needs.
type APICalls interface {
	ConnectionInfo() (params.DeployerConnectionValues, error)
}

// SimpleContext is a Context that manages unit deployments on the local system.
type SimpleContext struct {

	// api is used to get the current controller addresses at the time the
	// given unit is deployed.
	api APICalls

	// agentConfig returns the agent config for the machine agent that is
	// running the deployer.
	agentConfig agent.Config

	// discoverService is a surrogate for application.DiscoverService.
	discoverService func(string, common.Conf) (deployerService, error)

	// listServices is a surrogate for application.ListServices.
	listServices func() ([]string, error)
}

var _ Context = (*SimpleContext)(nil)

// recursiveChmod will change the permissions on all files and
// folders inside path
func recursiveChmod(path string, mode os.FileMode) error {
	walker := func(p string, fi os.FileInfo, err error) error {
		if _, err := os.Stat(p); err == nil {
			errPerm := os.Chmod(p, mode)
			if errPerm != nil {
				return errPerm
			}
		}
		return nil
	}
	if err := filepath.Walk(path, walker); err != nil {
		return err
	}
	return nil
}

// NewSimpleContext returns a new SimpleContext, acting on behalf of
// the specified deployer, that deploys unit agents.
// Paths to which agents and tools are installed are relative to dataDir.
func NewSimpleContext(agentConfig agent.Config, api APICalls) *SimpleContext {
	return &SimpleContext{
		api:         api,
		agentConfig: agentConfig,
		discoverService: func(name string, conf common.Conf) (deployerService, error) {
			return service.DiscoverService(name, conf)
		},
		listServices: func() ([]string, error) {
			return service.ListServices()
		},
	}
}

func (ctx *SimpleContext) AgentConfig() agent.Config {
	return ctx.agentConfig
}

func (ctx *SimpleContext) DeployUnit(unitName, initialPassword string) (err error) {
	// Check sanity.
	renderer, err := shell.NewRenderer("")
	if err != nil {
		return errors.Trace(err)
	}
	svc, err := ctx.service(unitName, renderer)
	if err != nil {
		return errors.Trace(err)
	}
	installed, err := svc.Installed()
	if err != nil {
		return errors.Trace(err)
	}
	if installed {
		return fmt.Errorf("unit %q is already deployed", unitName)
	}

	// Link the current tools for use by the new agent.
	tag := names.NewUnitTag(unitName)
	dataDir := ctx.agentConfig.DataDir()
	logDir := ctx.agentConfig.LogDir()
	current := version.Binary{
		Number: jujuversion.Current,
		Arch:   arch.HostArch(),
		Series: series.HostSeries(),
	}
	toolsDir := tools.ToolsDir(dataDir, tag.String())
	defer removeOnErr(&err, toolsDir)
	_, err = tools.ChangeAgentTools(dataDir, tag.String(), current)
	if err != nil {
		return errors.Trace(err)
	}

	result, err := ctx.api.ConnectionInfo()
	if err != nil {
		return errors.Trace(err)
	}
	logger.Debugf("state addresses: %q", result.StateAddresses)
	logger.Debugf("API addresses: %q", result.APIAddresses)
	containerType := ctx.agentConfig.Value(agent.ContainerType)
	namespace := ctx.agentConfig.Value(agent.Namespace)
	conf, err := agent.NewAgentConfig(
		agent.AgentConfigParams{
			Paths: agent.Paths{
				DataDir:         dataDir,
				LogDir:          logDir,
				MetricsSpoolDir: agent.DefaultPaths.MetricsSpoolDir,
			},
			UpgradedToVersion: jujuversion.Current,
			Tag:               tag,
			Password:          initialPassword,
			Nonce:             "unused",
			Model:             ctx.agentConfig.Model(),
			// TODO: remove the state addresses here and test when api only.
			StateAddresses: result.StateAddresses,
			APIAddresses:   result.APIAddresses,
			CACert:         ctx.agentConfig.CACert(),
			Values: map[string]string{
				agent.ContainerType: containerType,
				agent.Namespace:     namespace,
			},
		})
	if err != nil {
		return errors.Trace(err)
	}
	if err := conf.Write(); err != nil {
		return err
	}
	defer removeOnErr(&err, conf.Dir())

	// Install an init service that runs the unit agent.
	if err := service.InstallAndStart(svc); err != nil {
		return errors.Trace(err)
	}
	return nil
}

type deployerService interface {
	Installed() (bool, error)
	Install() error
	Remove() error
	Start() error
	Stop() error
}

// findUpstartJob tries to find an init system job matching the
// given unit name in one of these formats:
//   jujud-<deployer-tag>:<unit-tag>.conf (for compatibility)
//   jujud-<unit-tag>.conf (default)
func (ctx *SimpleContext) findInitSystemJob(unitName string) (deployerService, error) {
	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
	if err != nil {
		return nil, errors.Trace(err)
	}
	if job, ok := unitsAndJobs[unitName]; ok {
		return ctx.discoverService(job, common.Conf{})
	}
	return nil, errors.Errorf("unit %q is not deployed", unitName)
}

func (ctx *SimpleContext) RecallUnit(unitName string) error {
	svc, err := ctx.findInitSystemJob(unitName)
	if err != nil {
		return errors.Trace(err)
	}
	installed, err := svc.Installed()
	if err != nil {
		return errors.Trace(err)
	}
	if !installed {
		return errors.Errorf("unit %q is not deployed", unitName)
	}
	if err := svc.Stop(); err != nil {
		return err
	}
	if err := svc.Remove(); err != nil {
		return err
	}
	tag := names.NewUnitTag(unitName)
	dataDir := ctx.agentConfig.DataDir()
	agentDir := agent.Dir(dataDir, tag)
	// Recursivley change mode to 777 on windows to avoid
	// Operation not permitted errors when deleting the agentDir
	err = recursiveChmod(agentDir, os.FileMode(0777))
	if err != nil {
		return err
	}
	if err := os.RemoveAll(agentDir); err != nil {
		return err
	}
	// TODO(dfc) should take a Tag
	toolsDir := tools.ToolsDir(dataDir, tag.String())
	return os.Remove(toolsDir)
}

var deployedRe = regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))$")

func (ctx *SimpleContext) deployedUnitsInitSystemJobs() (map[string]string, error) {
	fis, err := ctx.listServices()
	if err != nil {
		return nil, err
	}
	if err != nil {
		return nil, err
	}
	installed := make(map[string]string)
	for _, fi := range fis {
		if groups := deployedRe.FindStringSubmatch(fi); len(groups) > 0 {
			unitName := groups[2] + "/" + groups[3]
			if !names.IsValidUnit(unitName) {
				continue
			}
			installed[unitName] = groups[1]
		}
	}
	return installed, nil
}

func (ctx *SimpleContext) DeployedUnits() ([]string, error) {
	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
	if err != nil {
		return nil, err
	}
	var installed []string
	for unitName := range unitsAndJobs {
		installed = append(installed, unitName)
	}
	return installed, nil
}

// service returns a service.Service corresponding to the specified
// unit.
func (ctx *SimpleContext) service(unitName string, renderer shell.Renderer) (deployerService, error) {
	tag := names.NewUnitTag(unitName).String()
	svcName := "jujud-" + tag

	info := service.NewAgentInfo(
		service.AgentKindUnit,
		unitName,
		ctx.agentConfig.DataDir(),
		ctx.agentConfig.LogDir(),
	)

	// TODO(thumper): 2013-09-02 bug 1219630
	// As much as I'd like to remove JujuContainerType now, it is still
	// needed as MAAS still needs it at this stage, and we can't fix
	// everything at once.
	containerType := ctx.agentConfig.Value(agent.ContainerType)

	conf := service.ContainerAgentConf(info, renderer, containerType)
	return ctx.discoverService(svcName, conf)
}

func removeOnErr(err *error, path string) {
	if *err != nil {
		if err := os.RemoveAll(path); err != nil {
			logger.Errorf("installer: cannot remove %q: %v", path, err)
		}
	}
}
<commit_msg>Fix comment<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package deployer

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"

	"github.com/juju/errors"
	"github.com/juju/utils/arch"
	"github.com/juju/utils/series"
	"github.com/juju/utils/shell"
	"github.com/juju/version"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/agent/tools"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/service"
	"github.com/juju/juju/service/common"
	jujuversion "github.com/juju/juju/version"
)

// TODO(ericsnow) Use errors.Trace, etc. in this file.

// APICalls defines the interface to the API that the simple context needs.
type APICalls interface {
	ConnectionInfo() (params.DeployerConnectionValues, error)
}

// SimpleContext is a Context that manages unit deployments on the local system.
type SimpleContext struct {

	// api is used to get the current controller addresses at the time the
	// given unit is deployed.
	api APICalls

	// agentConfig returns the agent config for the machine agent that is
	// running the deployer.
	agentConfig agent.Config

	// discoverService is a surrogate for service.DiscoverService.
	discoverService func(string, common.Conf) (deployerService, error)

	// listServices is a surrogate for service.ListServices.
	listServices func() ([]string, error)
}

var _ Context = (*SimpleContext)(nil)

// recursiveChmod will change the permissions on all files and
// folders inside path
func recursiveChmod(path string, mode os.FileMode) error {
	walker := func(p string, fi os.FileInfo, err error) error {
		if _, err := os.Stat(p); err == nil {
			errPerm := os.Chmod(p, mode)
			if errPerm != nil {
				return errPerm
			}
		}
		return nil
	}
	if err := filepath.Walk(path, walker); err != nil {
		return err
	}
	return nil
}

// NewSimpleContext returns a new SimpleContext, acting on behalf of
// the specified deployer, that deploys unit agents.
// Paths to which agents and tools are installed are relative to dataDir.
func NewSimpleContext(agentConfig agent.Config, api APICalls) *SimpleContext {
	return &SimpleContext{
		api:         api,
		agentConfig: agentConfig,
		discoverService: func(name string, conf common.Conf) (deployerService, error) {
			return service.DiscoverService(name, conf)
		},
		listServices: func() ([]string, error) {
			return service.ListServices()
		},
	}
}

func (ctx *SimpleContext) AgentConfig() agent.Config {
	return ctx.agentConfig
}

func (ctx *SimpleContext) DeployUnit(unitName, initialPassword string) (err error) {
	// Check sanity.
	renderer, err := shell.NewRenderer("")
	if err != nil {
		return errors.Trace(err)
	}
	svc, err := ctx.service(unitName, renderer)
	if err != nil {
		return errors.Trace(err)
	}
	installed, err := svc.Installed()
	if err != nil {
		return errors.Trace(err)
	}
	if installed {
		return fmt.Errorf("unit %q is already deployed", unitName)
	}

	// Link the current tools for use by the new agent.
	tag := names.NewUnitTag(unitName)
	dataDir := ctx.agentConfig.DataDir()
	logDir := ctx.agentConfig.LogDir()
	current := version.Binary{
		Number: jujuversion.Current,
		Arch:   arch.HostArch(),
		Series: series.HostSeries(),
	}
	toolsDir := tools.ToolsDir(dataDir, tag.String())
	defer removeOnErr(&err, toolsDir)
	_, err = tools.ChangeAgentTools(dataDir, tag.String(), current)
	if err != nil {
		return errors.Trace(err)
	}

	result, err := ctx.api.ConnectionInfo()
	if err != nil {
		return errors.Trace(err)
	}
	logger.Debugf("state addresses: %q", result.StateAddresses)
	logger.Debugf("API addresses: %q", result.APIAddresses)
	containerType := ctx.agentConfig.Value(agent.ContainerType)
	namespace := ctx.agentConfig.Value(agent.Namespace)
	conf, err := agent.NewAgentConfig(
		agent.AgentConfigParams{
			Paths: agent.Paths{
				DataDir:         dataDir,
				LogDir:          logDir,
				MetricsSpoolDir: agent.DefaultPaths.MetricsSpoolDir,
			},
			UpgradedToVersion: jujuversion.Current,
			Tag:               tag,
			Password:          initialPassword,
			Nonce:             "unused",
			Model:             ctx.agentConfig.Model(),
			// TODO: remove the state addresses here and test when api only.
			StateAddresses: result.StateAddresses,
			APIAddresses:   result.APIAddresses,
			CACert:         ctx.agentConfig.CACert(),
			Values: map[string]string{
				agent.ContainerType: containerType,
				agent.Namespace:     namespace,
			},
		})
	if err != nil {
		return errors.Trace(err)
	}
	if err := conf.Write(); err != nil {
		return err
	}
	defer removeOnErr(&err, conf.Dir())

	// Install an init service that runs the unit agent.
	if err := service.InstallAndStart(svc); err != nil {
		return errors.Trace(err)
	}
	return nil
}

type deployerService interface {
	Installed() (bool, error)
	Install() error
	Remove() error
	Start() error
	Stop() error
}

// findUpstartJob tries to find an init system job matching the
// given unit name in one of these formats:
//   jujud-<deployer-tag>:<unit-tag>.conf (for compatibility)
//   jujud-<unit-tag>.conf (default)
func (ctx *SimpleContext) findInitSystemJob(unitName string) (deployerService, error) {
	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
	if err != nil {
		return nil, errors.Trace(err)
	}
	if job, ok := unitsAndJobs[unitName]; ok {
		return ctx.discoverService(job, common.Conf{})
	}
	return nil, errors.Errorf("unit %q is not deployed", unitName)
}

func (ctx *SimpleContext) RecallUnit(unitName string) error {
	svc, err := ctx.findInitSystemJob(unitName)
	if err != nil {
		return errors.Trace(err)
	}
	installed, err := svc.Installed()
	if err != nil {
		return errors.Trace(err)
	}
	if !installed {
		return errors.Errorf("unit %q is not deployed", unitName)
	}
	if err := svc.Stop(); err != nil {
		return err
	}
	if err := svc.Remove(); err != nil {
		return err
	}
	tag := names.NewUnitTag(unitName)
	dataDir := ctx.agentConfig.DataDir()
	agentDir := agent.Dir(dataDir, tag)
	// Recursivley change mode to 777 on windows to avoid
	// Operation not permitted errors when deleting the agentDir
	err = recursiveChmod(agentDir, os.FileMode(0777))
	if err != nil {
		return err
	}
	if err := os.RemoveAll(agentDir); err != nil {
		return err
	}
	// TODO(dfc) should take a Tag
	toolsDir := tools.ToolsDir(dataDir, tag.String())
	return os.Remove(toolsDir)
}

var deployedRe = regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))$")

func (ctx *SimpleContext) deployedUnitsInitSystemJobs() (map[string]string, error) {
	fis, err := ctx.listServices()
	if err != nil {
		return nil, err
	}
	if err != nil {
		return nil, err
	}
	installed := make(map[string]string)
	for _, fi := range fis {
		if groups := deployedRe.FindStringSubmatch(fi); len(groups) > 0 {
			unitName := groups[2] + "/" + groups[3]
			if !names.IsValidUnit(unitName) {
				continue
			}
			installed[unitName] = groups[1]
		}
	}
	return installed, nil
}

func (ctx *SimpleContext) DeployedUnits() ([]string, error) {
	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
	if err != nil {
		return nil, err
	}
	var installed []string
	for unitName := range unitsAndJobs {
		installed = append(installed, unitName)
	}
	return installed, nil
}

// service returns a service.Service corresponding to the specified
// unit.
func (ctx *SimpleContext) service(unitName string, renderer shell.Renderer) (deployerService, error) {
	tag := names.NewUnitTag(unitName).String()
	svcName := "jujud-" + tag

	info := service.NewAgentInfo(
		service.AgentKindUnit,
		unitName,
		ctx.agentConfig.DataDir(),
		ctx.agentConfig.LogDir(),
	)

	// TODO(thumper): 2013-09-02 bug 1219630
	// As much as I'd like to remove JujuContainerType now, it is still
	// needed as MAAS still needs it at this stage, and we can't fix
	// everything at once.
	containerType := ctx.agentConfig.Value(agent.ContainerType)

	conf := service.ContainerAgentConf(info, renderer, containerType)
	return ctx.discoverService(svcName, conf)
}

func removeOnErr(err *error, path string) {
	if *err != nil {
		if err := os.RemoveAll(path); err != nil {
			logger.Errorf("installer: cannot remove %q: %v", path, err)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2020 Tigera, 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 conntrack

import (
	"net"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/projectcalico/felix/bpf"
	"github.com/projectcalico/felix/timeshim"
)

type Timeouts struct {
	CreationGracePeriod time.Duration

	TCPPreEstablished time.Duration
	TCPEstablished    time.Duration
	TCPFinsSeen       time.Duration
	TCPResetSeen      time.Duration

	UDPLastSeen time.Duration

	ICMPLastSeen time.Duration
}

func DefaultTimeouts() Timeouts {
	return Timeouts{
		CreationGracePeriod: 10 * time.Second,
		TCPPreEstablished:   20 * time.Second,
		TCPEstablished:      time.Hour,
		TCPFinsSeen:         30 * time.Second,
		TCPResetSeen:        40 * time.Second,
		UDPLastSeen:         60 * time.Second,
		ICMPLastSeen:        5 * time.Second,
	}
}

// ScanVerdict represents the set of values returned by EntryScan
type ScanVerdict int

const (
	// ScanVerdictOK means entry is fine and should remain
	ScanVerdictOK ScanVerdict = iota
	// ScanVerdictDelete means entry should be deleted
	ScanVerdictDelete
)

// EntryGet is a function prototype provided to EntryScanner in case it needs to
// evaluate other entries to make a verdict
type EntryGet func(Key) (Value, error)

// EntryScanner is a function prototype to be called on every entry by the scanner
type EntryScanner func(Key, Value, EntryGet) ScanVerdict

// Scanner iterates over a provided conntrack map and call a set of EntryScanner
// functions on each entry in the order as they were passed to NewScanner. If
// any of the EntryScanner returns ScanVerdictDelete, it deletes the entry, does
// not call any other EntryScanner and continues the iteration.
//
// It provides a delete-save iteration over the conntrack table for multiple
// evaluation functions, to keep their implementation simpler.
type Scanner struct {
	ctMap    bpf.Map
	scanners []EntryScanner
}

// NewScanner returns a scanner for the given conntrack map and the set of
// EntryScanner. They are executed in the provided order on each entry.
func NewScanner(ctMap bpf.Map, scanners ...EntryScanner) *Scanner {
	return &Scanner{
		ctMap:    ctMap,
		scanners: scanners,
	}
}

// Scan executes a scanning iteration
func (s *Scanner) Scan() {
	debug := log.GetLevel() >= log.DebugLevel

	var ctKey Key
	var ctVal Value

	err := s.ctMap.Iter(func(k, v []byte) bpf.IteratorAction {
		copy(ctKey[:], k[:])
		copy(ctVal[:], v[:])

		if debug {
			log.WithFields(log.Fields{
				"key":   ctKey,
				"entry": ctVal,
			}).Debug("Examining conntrack entry")
		}

		for _, scanner := range s.scanners {
			if verdict := scanner(ctKey, ctVal, s.get); verdict == ScanVerdictDelete {
				if debug {
					log.Debug("Deleting conntrack entry.")
				}
				return bpf.IterDelete
			}
		}
		return bpf.IterNone
	})

	if err != nil {
		log.WithError(err).Warn("Failed to iterate over conntrack map")
	}
}

func (s *Scanner) get(k Key) (Value, error) {
	v, err := s.ctMap.Get(k.AsBytes())

	if err != nil {
		return Value{}, err
	}

	return ValueFromBytes(v), nil
}

type LivenessScanner struct {
	timeouts Timeouts
	dsr      bool
	time     timeshim.Interface

	// goTimeOfLastKTimeLookup is the go timestamp of the last time we looked up the kernel time.
	// We cache the kernel time because it's expensive to look up (vs looking up a go timestamp which uses vdso).
	goTimeOfLastKTimeLookup time.Time
	// cachedKTime is the most recent kernel time.
	cachedKTime int64
}

func NewLivenessScanner(timeouts Timeouts, dsr bool, opts ...LivenessScannerOpt) *LivenessScanner {
	ls := &LivenessScanner{
		timeouts: timeouts,
		dsr:      dsr,
		time:     timeshim.RealTime(),
	}
	for _, opt := range opts {
		opt(ls)
	}
	return ls
}

type LivenessScannerOpt func(ls *LivenessScanner)

func WithTimeShim(shim timeshim.Interface) LivenessScannerOpt {
	return func(ls *LivenessScanner) {
		ls.time = shim
	}
}

func (l *LivenessScanner) ScanEntry(ctKey Key, ctVal Value, get EntryGet) ScanVerdict {
	if l.cachedKTime == 0 || l.time.Since(l.goTimeOfLastKTimeLookup) > time.Second {
		l.cachedKTime = l.time.KTimeNanos()
		l.goTimeOfLastKTimeLookup = l.time.Now()
	}
	now := l.cachedKTime

	debug := log.GetLevel() >= log.DebugLevel

	switch ctVal.Type() {
	case TypeNATForward:
		// Look up the reverse entry, where we do the book-keeping.
		revEntry, err := get(ctVal.ReverseNATKey())
		if err != nil && bpf.IsNotExists(err) {
			// Forward entry exists but no reverse entry. We might have come across the reverse
			// entry first and removed it. It is useless on its own, so delete it now.
			//
			// N.B. BPF code always creates REV entry before FWD entry, therefore if the REV
			// entry does not exist now, we are not racing with the BPF code, we must have
			// removed the entry or there is some external inconsistency. In either case, the
			// FWD entry should be removed.
			log.Info("Found a forward NAT conntrack entry with no reverse entry, removing...")
			return ScanVerdictDelete
		} else if err != nil {
			log.WithError(err).Warn("Failed to look up conntrack entry.")
			return ScanVerdictOK
		}
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), revEntry); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired conntrack forward-NAT entry")
			}
			return ScanVerdictDelete
			// do not delete the reverse entry yet to avoid breaking the iterating
			// over the map.  We must not delete other than the current key. We remove
			// it once we come across it again.
		}
	case TypeNATReverse:
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), ctVal); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired conntrack reverse-NAT entry")
			}
			return ScanVerdictDelete
		}
	case TypeNormal:
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), ctVal); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired normal conntrack entry")
			}
			return ScanVerdictDelete
		}
	default:
		log.WithField("type", ctVal.Type()).Warn("Unknown conntrack entry type!")
	}

	return ScanVerdictOK
}

func (l *LivenessScanner) EntryExpired(nowNanos int64, proto uint8, entry Value) (reason string, expired bool) {
	sinceCreation := time.Duration(nowNanos - entry.Created())
	if sinceCreation < l.timeouts.CreationGracePeriod {
		log.Debug("Conntrack entry in creation grace period. Ignoring.")
		return
	}
	age := time.Duration(nowNanos - entry.LastSeen())
	switch proto {
	case ProtoTCP:
		dsr := entry.IsForwardDSR()
		data := entry.Data()
		rstSeen := data.RSTSeen()
		if rstSeen && age > l.timeouts.TCPResetSeen {
			return "RST seen", true
		}
		finsSeen := (dsr && data.FINsSeenDSR()) || data.FINsSeen()
		if finsSeen && age > l.timeouts.TCPFinsSeen {
			// Both legs have been finished, tear down.
			return "FINs seen", true
		}
		if data.Established() || dsr {
			if age > l.timeouts.TCPEstablished {
				return "no traffic on established flow for too long", true
			}
		} else {
			if age > l.timeouts.TCPPreEstablished {
				return "no traffic on pre-established flow for too long", true
			}
		}
		return "", false
	case ProtoICMP:
		if age > l.timeouts.ICMPLastSeen {
			return "no traffic on ICMP flow for too long", true
		}
	default:
		// FIXME separate timeouts for non-UDP IP traffic?
		if age > l.timeouts.UDPLastSeen {
			return "no traffic on UDP flow for too long", true
		}
	}
	return "", false
}

// NATChecker returns true a given combination of frontend-backend exists
type NATChecker func(frontIP net.IP, frontPort uint16, backIP net.IP, backPort uint16, proto uint8) bool

// NewStaleNATScanner returns an EntryScanner that checks if entries have
// exisitng NAT entries using the provided NATChecker and if not, it deletes
// them.
func NewStaleNATScanner(frontendHasBackend NATChecker) EntryScanner {
	debug := log.GetLevel() >= log.DebugLevel

	return func(k Key, v Value, _ EntryGet) ScanVerdict {
		switch v.Type() {
		case TypeNormal:
			// skip non-NAT entry

		case TypeNATReverse:
			proto := k.Proto()
			ipA := k.AddrA()
			ipB := k.AddrB()

			portA := k.PortA()
			portB := k.PortB()

			svcIP := v.OrigIP()
			svcPort := v.OrigPort()

			// We cannot tell which leg is EP and which is the client, we must
			// try both. If there is a record for one of them, it is still most
			// likely an active entry.
			if !frontendHasBackend(svcIP, svcPort, ipA, portA, proto) &&
				!frontendHasBackend(svcIP, svcPort, ipB, portB, proto) {
				if debug {
					log.WithField("key", k).Debugf("TypeNATReverse is stale")
				}
				return ScanVerdictDelete
			}
			if debug {
				log.WithField("key", k).Debugf("TypeNATReverse still active")
			}

		case TypeNATForward:
			proto := k.Proto()
			kA := k.AddrA()
			kAport := k.PortA()
			kB := k.AddrB()
			kBport := k.PortB()
			revKey := v.ReverseNATKey()
			revA := revKey.AddrA()
			revAport := revKey.PortA()
			revB := revKey.AddrB()
			revBport := revKey.PortB()

			var (
				svcIP, epIP     net.IP
				svcPort, epPort uint16
			)

			// Because client IP/Port are both in fwd key and rev key, we can
			// can tell which one it is and thus determine exactly meaning of
			// the other values.
			if kA.Equal(revA) && kAport == revAport {
				epIP = revB
				epPort = revBport
				svcIP = kB
				svcPort = kBport
			} else if kB.Equal(revA) && kBport == revAport {
				epIP = revB
				epPort = revBport
				svcIP = kA
				svcPort = kAport
			} else if kA.Equal(revB) && kAport == revBport {
				epIP = revA
				epPort = revAport
				svcIP = kB
				svcPort = kBport
			} else if kB.Equal(revB) && kBport == revBport {
				epIP = revA
				epPort = revAport
				svcIP = kA
				svcPort = kAport
			} else {
				log.WithFields(log.Fields{"key": k, "value": v}).Error("Mismatch between key and rev key")
				return ScanVerdictOK // don't touch, will get deleted when expired
			}

			if !frontendHasBackend(svcIP, svcPort, epIP, epPort, proto) {
				if debug {
					log.WithField("key", k).Debugf("TypeNATForward is stale")
				}
				return ScanVerdictDelete
			}
			if debug {
				log.WithField("key", k).Debugf("TypeNATForward still active")
			}

		default:
			log.WithField("conntrack.Value.Type()", v.Type()).Warn("Unknown type")
		}

		return ScanVerdictOK
	}
}
<commit_msg>Fix spammy log from conntrack cleanup.<commit_after>// Copyright (c) 2020 Tigera, 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 conntrack

import (
	"net"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/projectcalico/felix/bpf"
	"github.com/projectcalico/felix/timeshim"
)

type Timeouts struct {
	CreationGracePeriod time.Duration

	TCPPreEstablished time.Duration
	TCPEstablished    time.Duration
	TCPFinsSeen       time.Duration
	TCPResetSeen      time.Duration

	UDPLastSeen time.Duration

	ICMPLastSeen time.Duration
}

func DefaultTimeouts() Timeouts {
	return Timeouts{
		CreationGracePeriod: 10 * time.Second,
		TCPPreEstablished:   20 * time.Second,
		TCPEstablished:      time.Hour,
		TCPFinsSeen:         30 * time.Second,
		TCPResetSeen:        40 * time.Second,
		UDPLastSeen:         60 * time.Second,
		ICMPLastSeen:        5 * time.Second,
	}
}

// ScanVerdict represents the set of values returned by EntryScan
type ScanVerdict int

const (
	// ScanVerdictOK means entry is fine and should remain
	ScanVerdictOK ScanVerdict = iota
	// ScanVerdictDelete means entry should be deleted
	ScanVerdictDelete
)

// EntryGet is a function prototype provided to EntryScanner in case it needs to
// evaluate other entries to make a verdict
type EntryGet func(Key) (Value, error)

// EntryScanner is a function prototype to be called on every entry by the scanner
type EntryScanner func(Key, Value, EntryGet) ScanVerdict

// Scanner iterates over a provided conntrack map and call a set of EntryScanner
// functions on each entry in the order as they were passed to NewScanner. If
// any of the EntryScanner returns ScanVerdictDelete, it deletes the entry, does
// not call any other EntryScanner and continues the iteration.
//
// It provides a delete-save iteration over the conntrack table for multiple
// evaluation functions, to keep their implementation simpler.
type Scanner struct {
	ctMap    bpf.Map
	scanners []EntryScanner
}

// NewScanner returns a scanner for the given conntrack map and the set of
// EntryScanner. They are executed in the provided order on each entry.
func NewScanner(ctMap bpf.Map, scanners ...EntryScanner) *Scanner {
	return &Scanner{
		ctMap:    ctMap,
		scanners: scanners,
	}
}

// Scan executes a scanning iteration
func (s *Scanner) Scan() {
	debug := log.GetLevel() >= log.DebugLevel

	var ctKey Key
	var ctVal Value

	err := s.ctMap.Iter(func(k, v []byte) bpf.IteratorAction {
		copy(ctKey[:], k[:])
		copy(ctVal[:], v[:])

		if debug {
			log.WithFields(log.Fields{
				"key":   ctKey,
				"entry": ctVal,
			}).Debug("Examining conntrack entry")
		}

		for _, scanner := range s.scanners {
			if verdict := scanner(ctKey, ctVal, s.get); verdict == ScanVerdictDelete {
				if debug {
					log.Debug("Deleting conntrack entry.")
				}
				return bpf.IterDelete
			}
		}
		return bpf.IterNone
	})

	if err != nil {
		log.WithError(err).Warn("Failed to iterate over conntrack map")
	}
}

func (s *Scanner) get(k Key) (Value, error) {
	v, err := s.ctMap.Get(k.AsBytes())

	if err != nil {
		return Value{}, err
	}

	return ValueFromBytes(v), nil
}

type LivenessScanner struct {
	timeouts Timeouts
	dsr      bool
	time     timeshim.Interface

	// goTimeOfLastKTimeLookup is the go timestamp of the last time we looked up the kernel time.
	// We cache the kernel time because it's expensive to look up (vs looking up a go timestamp which uses vdso).
	goTimeOfLastKTimeLookup time.Time
	// cachedKTime is the most recent kernel time.
	cachedKTime int64
}

func NewLivenessScanner(timeouts Timeouts, dsr bool, opts ...LivenessScannerOpt) *LivenessScanner {
	ls := &LivenessScanner{
		timeouts: timeouts,
		dsr:      dsr,
		time:     timeshim.RealTime(),
	}
	for _, opt := range opts {
		opt(ls)
	}
	return ls
}

type LivenessScannerOpt func(ls *LivenessScanner)

func WithTimeShim(shim timeshim.Interface) LivenessScannerOpt {
	return func(ls *LivenessScanner) {
		ls.time = shim
	}
}

func (l *LivenessScanner) ScanEntry(ctKey Key, ctVal Value, get EntryGet) ScanVerdict {
	if l.cachedKTime == 0 || l.time.Since(l.goTimeOfLastKTimeLookup) > time.Second {
		l.cachedKTime = l.time.KTimeNanos()
		l.goTimeOfLastKTimeLookup = l.time.Now()
	}
	now := l.cachedKTime

	debug := log.GetLevel() >= log.DebugLevel

	switch ctVal.Type() {
	case TypeNATForward:
		// Look up the reverse entry, where we do the book-keeping.
		revEntry, err := get(ctVal.ReverseNATKey())
		if err != nil && bpf.IsNotExists(err) {
			// Forward entry exists but no reverse entry. We might have come across the reverse
			// entry first and removed it. It is useless on its own, so delete it now.
			//
			// N.B. BPF code always creates REV entry before FWD entry, therefore if the REV
			// entry does not exist now, we are not racing with the BPF code, we must have
			// removed the entry or there is some external inconsistency. In either case, the
			// FWD entry should be removed.
			log.Debug("Found a forward NAT conntrack entry with no reverse entry, removing...")
			return ScanVerdictDelete
		} else if err != nil {
			log.WithError(err).Warn("Failed to look up conntrack entry.")
			return ScanVerdictOK
		}
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), revEntry); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired conntrack forward-NAT entry")
			}
			return ScanVerdictDelete
			// do not delete the reverse entry yet to avoid breaking the iterating
			// over the map.  We must not delete other than the current key. We remove
			// it once we come across it again.
		}
	case TypeNATReverse:
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), ctVal); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired conntrack reverse-NAT entry")
			}
			return ScanVerdictDelete
		}
	case TypeNormal:
		if reason, expired := l.EntryExpired(now, ctKey.Proto(), ctVal); expired {
			if debug {
				log.WithField("reason", reason).Debug("Deleting expired normal conntrack entry")
			}
			return ScanVerdictDelete
		}
	default:
		log.WithField("type", ctVal.Type()).Warn("Unknown conntrack entry type!")
	}

	return ScanVerdictOK
}

func (l *LivenessScanner) EntryExpired(nowNanos int64, proto uint8, entry Value) (reason string, expired bool) {
	sinceCreation := time.Duration(nowNanos - entry.Created())
	if sinceCreation < l.timeouts.CreationGracePeriod {
		log.Debug("Conntrack entry in creation grace period. Ignoring.")
		return
	}
	age := time.Duration(nowNanos - entry.LastSeen())
	switch proto {
	case ProtoTCP:
		dsr := entry.IsForwardDSR()
		data := entry.Data()
		rstSeen := data.RSTSeen()
		if rstSeen && age > l.timeouts.TCPResetSeen {
			return "RST seen", true
		}
		finsSeen := (dsr && data.FINsSeenDSR()) || data.FINsSeen()
		if finsSeen && age > l.timeouts.TCPFinsSeen {
			// Both legs have been finished, tear down.
			return "FINs seen", true
		}
		if data.Established() || dsr {
			if age > l.timeouts.TCPEstablished {
				return "no traffic on established flow for too long", true
			}
		} else {
			if age > l.timeouts.TCPPreEstablished {
				return "no traffic on pre-established flow for too long", true
			}
		}
		return "", false
	case ProtoICMP:
		if age > l.timeouts.ICMPLastSeen {
			return "no traffic on ICMP flow for too long", true
		}
	default:
		// FIXME separate timeouts for non-UDP IP traffic?
		if age > l.timeouts.UDPLastSeen {
			return "no traffic on UDP flow for too long", true
		}
	}
	return "", false
}

// NATChecker returns true a given combination of frontend-backend exists
type NATChecker func(frontIP net.IP, frontPort uint16, backIP net.IP, backPort uint16, proto uint8) bool

// NewStaleNATScanner returns an EntryScanner that checks if entries have
// exisitng NAT entries using the provided NATChecker and if not, it deletes
// them.
func NewStaleNATScanner(frontendHasBackend NATChecker) EntryScanner {
	debug := log.GetLevel() >= log.DebugLevel

	return func(k Key, v Value, _ EntryGet) ScanVerdict {
		switch v.Type() {
		case TypeNormal:
			// skip non-NAT entry

		case TypeNATReverse:
			proto := k.Proto()
			ipA := k.AddrA()
			ipB := k.AddrB()

			portA := k.PortA()
			portB := k.PortB()

			svcIP := v.OrigIP()
			svcPort := v.OrigPort()

			// We cannot tell which leg is EP and which is the client, we must
			// try both. If there is a record for one of them, it is still most
			// likely an active entry.
			if !frontendHasBackend(svcIP, svcPort, ipA, portA, proto) &&
				!frontendHasBackend(svcIP, svcPort, ipB, portB, proto) {
				if debug {
					log.WithField("key", k).Debugf("TypeNATReverse is stale")
				}
				return ScanVerdictDelete
			}
			if debug {
				log.WithField("key", k).Debugf("TypeNATReverse still active")
			}

		case TypeNATForward:
			proto := k.Proto()
			kA := k.AddrA()
			kAport := k.PortA()
			kB := k.AddrB()
			kBport := k.PortB()
			revKey := v.ReverseNATKey()
			revA := revKey.AddrA()
			revAport := revKey.PortA()
			revB := revKey.AddrB()
			revBport := revKey.PortB()

			var (
				svcIP, epIP     net.IP
				svcPort, epPort uint16
			)

			// Because client IP/Port are both in fwd key and rev key, we can
			// can tell which one it is and thus determine exactly meaning of
			// the other values.
			if kA.Equal(revA) && kAport == revAport {
				epIP = revB
				epPort = revBport
				svcIP = kB
				svcPort = kBport
			} else if kB.Equal(revA) && kBport == revAport {
				epIP = revB
				epPort = revBport
				svcIP = kA
				svcPort = kAport
			} else if kA.Equal(revB) && kAport == revBport {
				epIP = revA
				epPort = revAport
				svcIP = kB
				svcPort = kBport
			} else if kB.Equal(revB) && kBport == revBport {
				epIP = revA
				epPort = revAport
				svcIP = kA
				svcPort = kAport
			} else {
				log.WithFields(log.Fields{"key": k, "value": v}).Error("Mismatch between key and rev key")
				return ScanVerdictOK // don't touch, will get deleted when expired
			}

			if !frontendHasBackend(svcIP, svcPort, epIP, epPort, proto) {
				if debug {
					log.WithField("key", k).Debugf("TypeNATForward is stale")
				}
				return ScanVerdictDelete
			}
			if debug {
				log.WithField("key", k).Debugf("TypeNATForward still active")
			}

		default:
			log.WithField("conntrack.Value.Type()", v.Type()).Warn("Unknown type")
		}

		return ScanVerdictOK
	}
}
<|endoftext|>


<commit_before>package dondeestas

import (
	"bytes"
	"encoding/json"
	"fmt"
	// "io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	// "math/rand"
	// "log"
)

func TestRouting_GetJson(t *testing.T) {
	expectedPerson, _ := createRandomPerson()
	expectedPersonJson, _ := json.Marshal(expectedPerson)
	expectedPersonStr := string(expectedPersonJson)
	req := httptest.NewRequest("GET", "http://blah.com/foo", bytes.NewBufferString(expectedPersonStr))

	var person Person
	if err := getJson(req.Body, person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
	}
}

func TestRouting_PostJson(t *testing.T) {
	expectedPerson, _ := createRandomPerson()
	expectedStatus := http.StatusOK
	response := httptest.NewRecorder()
	postJson(response, expectedStatus, expectedPerson)

	if response.Code != expectedStatus {
		t.Fatalf("Expected http status %d but found %d", expectedStatus, response.Code)
	}

	fmt.Print("'")
	fmt.Print(response.Body)
	fmt.Println("'")
	var person Person
	if err := json.Unmarshal(response.Body.Bytes(), &person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
	} else if !arePersonEqual(expectedPerson, &person) {
		t.Fatal("Did not receive expected person")
	}
	/*
	if err := getJson(ioutil.NopCloser(response.Body), person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
		// TODO: } else if !arePersonEqual(expectedPerson, &person) {
		// t.Fatal("Did not receive expected person")
	}*/
}
/*
func TestRouting_UpdatePersonHandler(t *testing.T) {
	log := log.New(os.Stdout, "", 0)
	response := httptest.NewRecorder()

	expectedPerson, _ := createRandomPerson()
    req := httptest.NewRequest("GET", createRandomString(), bytes.NewBufferString(expectedPersonStr))

	db, server, _ := createRandomDbClient()
	defer server.Close()

	UpdatePersonHandler(log, db, response, req)
}

func TestRouting_PersonRequestHandler(t *testing.T) {
	log := log.New(os.Stdout, "", 0)
	response := httptest.NewRecorder()

    req := httptest.NewRequest("GET", createRandomString(), bytes.NewBufferString(expectedPersonStr))

	db, server, _ := createRandomDbClient()
	defer server.Close()

	PersonRequestHandler(log, db, response, req)
}

func TestRouting_New(t *testing.T) { // TODO TEST FOR BAD INPUT VALUES
	port := rand.Int()
	log := log.New(os.Stdout, "", 0)
	db, server, _ := createRandomDbClient()
	defer server.Close()

	New(log *log.Logger, port int, db *dbclient)
}
*/
<commit_msg>Much more complete testing of the router file<commit_after>package dondeestas

import (
	"bytes"
	"encoding/json"
	"log"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
)

func getExpectedPersonRequest() (*http.Request, *Person, *httptest.ResponseRecorder) {
	expectedPerson, _ := createRandomPerson()
	expectedPersonJson, _ := json.Marshal(expectedPerson)
	expectedPersonStr := string(expectedPersonJson)
	req := httptest.NewRequest("GET", "http://"+createRandomString(), bytes.NewBufferString(expectedPersonStr))

	return req, expectedPerson, httptest.NewRecorder()
}

func TestRouting_GetJson(t *testing.T) {
	expectedPerson, _ := createRandomPerson()
	expectedPersonJson, _ := json.Marshal(expectedPerson)
	expectedPersonStr := string(expectedPersonJson)
	req := httptest.NewRequest("GET", "http://blah.com/foo", bytes.NewBufferString(expectedPersonStr))

	var person Person
	if err := getJson(req.Body, person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
	}
}

func TestRouting_PostJson(t *testing.T) {
	expectedPerson, _ := createRandomPerson()
	expectedStatus := http.StatusOK
	response := httptest.NewRecorder()
	postJson(response, expectedStatus, expectedPerson)

	if response.Code != expectedStatus {
		t.Fatalf("Expected http status %d but found %d", expectedStatus, response.Code)
	}

	var person Person
	if err := json.Unmarshal(response.Body.Bytes(), &person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
	} else if !arePersonEqual(expectedPerson, &person) {
		t.Fatal("Did not receive expected person")
	}
	// TODO: more?
}

func TestRouting_UpdatePersonHandler(t *testing.T) {
	log := log.New(os.Stdout, "", 0)
	response := httptest.NewRecorder()

	expectedPerson, _ := createRandomPerson()
	expectedPersonJson, _ := json.Marshal(expectedPerson)
	expectedPersonStr := string(expectedPersonJson)
	req := httptest.NewRequest("GET", "http://"+createRandomString(), bytes.NewBufferString(expectedPersonStr))

	db, server, _ := createRandomDbClient()
	defer server.Close()

	UpdatePersonHandler(log, db, response, req)

	if response.Code != http.StatusCreated {
		t.Errorf("Did not get expected HTTP status code. Instead got: %d", response.Code)
	}

	if person, err := (*db).Get(expectedPerson.Id); err != nil {
		t.Fatalf("Encountered error when retrieving person: %s", err)
	} else if !arePersonEqual(expectedPerson, person) {
		t.Fatal("Retrieved Person is not equivalent to the expected Person")
	}

	// TODO: more?
}

func TestRouting_PersonRequestHandler(t *testing.T) {
	log := log.New(os.Stdout, "", 0)
	response := httptest.NewRecorder()

	expectedPerson, _ := createRandomPerson()

	var dataReq PersonDataRequest
	dataReq.Ids = make([]string, 1)
	dataReq.Ids[0] = expectedPerson.Id
	personDataRequestJson, _ := json.Marshal(dataReq)
	personDataRequestStr := string(personDataRequestJson)
	req := httptest.NewRequest("GET", "http://"+createRandomString(), bytes.NewBufferString(personDataRequestStr))

	db, server, _ := createRandomDbClient()
	defer server.Close()

	(*db).Create(*expectedPerson)

	PersonRequestHandler(log, db, response, req)

	// TODO: check status code
	// TODO: check number of people returned

	var person PersonDataResponse
	if err := json.Unmarshal(response.Body.Bytes(), &person); err != nil {
		t.Fatalf("Encountered error when retrieving json from string: %s", err)
	} else if !arePersonEqual(expectedPerson, &person.People[0]) {
		t.Fatal("Did not receive expected person")
	}

	// TODO: more?
	/*
		dataReq.Ids = append(dataReq.Ids, createRandomString())
		personDataRequestJson, _ = json.Marshal(dataReq)
		personDataRequestStr = string(personDataRequestJson)
		req = httptest.NewRequest("GET", "http://"+createRandomString(), bytes.NewBufferString(personDataRequestStr))
	*/
}

func TestRouting_ListenAndServe(t *testing.T) {
	t.Skip("TODO")
	port := rand.Int()
	log := log.New(os.Stdout, "", 0)
	db, server, _ := createRandomDbClient()
	defer server.Close()

	// TODO: use a goroutine to do some tests
	go ListenAndServe(log, port, db)
}
<|endoftext|>


<commit_before>/*
 * A Go package to copy a Dataset
 *
 * Copyright (C) 2017 Lawrence Woodman <lwoodman@vlifesystems.com>
 *
 * Licensed under an MIT licence.  Please see LICENCE.md for details.
 */

// Package dcopy copies a Dataset so that you can work consistently on
// the same Dataset.  This is important where a database is likely to be
// updated while you are working on it.  The copy of the database is stored
// in a CSV file located in a temporary directory.
package dcopy

import (
	"encoding/csv"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/lawrencewoodman/ddataset"
	"github.com/lawrencewoodman/ddataset/dcsv"
	"github.com/lawrencewoodman/ddataset/internal"
)

// DCopy represents a copy of a Dataset
type DCopy struct {
	dataset    ddataset.Dataset
	tmpDir     string
	isReleased bool
	numRecords int64
}

// DCopyConn represents a connection to a DCopy Dataset
type DCopyConn struct {
	conn ddataset.Conn
	err  error
}

// New creates a new DCopy Dataset which will be a copy of the Dataset
// supplied at the time it is run. Please note that this creates a file
// on the disk containing a copy of the supplied Dataset.  The copy is
// created in a sub-directory of tmpDir.  If tmpDir is the empty string,
// then it uses the default system temporary directory.
func New(dataset ddataset.Dataset, tmpDir string) (ddataset.Dataset, error) {
	tmpDir, err := ioutil.TempDir(tmpDir, "dcopy")
	if err != nil {
		return nil, err
	}
	copyFilename := filepath.Join(tmpDir, "copy.csv")
	f, err := os.Create(copyFilename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	w := csv.NewWriter(f)

	conn, err := dataset.Open()
	if err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}
	defer conn.Close()

	strRecord := make([]string, len(dataset.Fields()))
	for conn.Next() {
		record := conn.Read()
		for i, f := range dataset.Fields() {
			strRecord[i] = record[f].String()
		}
		if err := w.Write(strRecord); err != nil {
			os.RemoveAll(tmpDir)
			return nil, fmt.Errorf("error writing record to csv copy: %s", err)
		}
	}

	if err := conn.Err(); err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}

	return &DCopy{
		dataset:    dcsv.New(copyFilename, false, ',', dataset.Fields()),
		tmpDir:     tmpDir,
		isReleased: false,
		numRecords: -1,
	}, nil
}

// Open creates a connection to the Dataset
func (d *DCopy) Open() (ddataset.Conn, error) {
	if d.isReleased {
		return nil, ddataset.ErrReleased
	}
	conn, err := d.dataset.Open()
	if err != nil {
		return nil, err
	}
	return &DCopyConn{
		conn: conn,
		err:  nil,
	}, nil
}

// Fields returns the field names used by the Dataset
func (d *DCopy) Fields() []string {
	if d.isReleased {
		return []string{}
	}
	return d.dataset.Fields()
}

// NumRecords returns the number of records in the Dataset.  If there is
// a problem getting the number of records it returns -1.
func (d *DCopy) NumRecords() int64 {
	if d.numRecords != -1 {
		return d.numRecords
	}
	d.numRecords = internal.CountNumRecords(d)
	return d.numRecords
}

// Release releases any resources associated with the Dataset d,
// rendering it unusable in the future.  In this case it deletes
// the temporary copy of the Dataset.
func (d *DCopy) Release() error {
	if !d.isReleased {
		err := os.RemoveAll(d.tmpDir)
		if err == nil {
			d.isReleased = true
		}
		return err
	}
	return ddataset.ErrReleased
}

// Next returns whether there is a Record to be Read
func (c *DCopyConn) Next() bool {
	return c.conn.Next()
}

// Err returns any errors from the connection
func (c *DCopyConn) Err() error {
	return c.conn.Err()
}

// Read returns the current Record
func (c *DCopyConn) Read() ddataset.Record {
	return c.conn.Read()
}

// Close closes the connection and deletes the copy
func (c *DCopyConn) Close() error {
	return c.conn.Close()
}
<commit_msg>Speed up DCopy NumRecords() by counting records while copying<commit_after>/*
 * A Go package to copy a Dataset
 *
 * Copyright (C) 2017 Lawrence Woodman <lwoodman@vlifesystems.com>
 *
 * Licensed under an MIT licence.  Please see LICENCE.md for details.
 */

// Package dcopy copies a Dataset so that you can work consistently on
// the same Dataset.  This is important where a database is likely to be
// updated while you are working on it.  The copy of the database is stored
// in a CSV file located in a temporary directory.
package dcopy

import (
	"encoding/csv"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/lawrencewoodman/ddataset"
	"github.com/lawrencewoodman/ddataset/dcsv"
)

// DCopy represents a copy of a Dataset
type DCopy struct {
	dataset    ddataset.Dataset
	tmpDir     string
	isReleased bool
	numRecords int64
}

// DCopyConn represents a connection to a DCopy Dataset
type DCopyConn struct {
	conn ddataset.Conn
	err  error
}

// New creates a new DCopy Dataset which will be a copy of the Dataset
// supplied at the time it is run. Please note that this creates a file
// on the disk containing a copy of the supplied Dataset.  The copy is
// created in a sub-directory of tmpDir.  If tmpDir is the empty string,
// then it uses the default system temporary directory.
func New(dataset ddataset.Dataset, tmpDir string) (ddataset.Dataset, error) {
	tmpDir, err := ioutil.TempDir(tmpDir, "dcopy")
	if err != nil {
		return nil, err
	}
	copyFilename := filepath.Join(tmpDir, "copy.csv")
	f, err := os.Create(copyFilename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	w := csv.NewWriter(f)

	conn, err := dataset.Open()
	if err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}
	defer conn.Close()

	numRecords := int64(0)
	strRecord := make([]string, len(dataset.Fields()))
	for conn.Next() {
		numRecords++
		record := conn.Read()
		for i, f := range dataset.Fields() {
			strRecord[i] = record[f].String()
		}
		if err := w.Write(strRecord); err != nil {
			os.RemoveAll(tmpDir)
			return nil, fmt.Errorf("error writing record to csv copy: %s", err)
		}
	}

	if err := conn.Err(); err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		os.RemoveAll(tmpDir)
		return nil, err
	}

	return &DCopy{
		dataset:    dcsv.New(copyFilename, false, ',', dataset.Fields()),
		tmpDir:     tmpDir,
		isReleased: false,
		numRecords: numRecords,
	}, nil
}

// Open creates a connection to the Dataset
func (d *DCopy) Open() (ddataset.Conn, error) {
	if d.isReleased {
		return nil, ddataset.ErrReleased
	}
	conn, err := d.dataset.Open()
	if err != nil {
		return nil, err
	}
	return &DCopyConn{
		conn: conn,
		err:  nil,
	}, nil
}

// Fields returns the field names used by the Dataset
func (d *DCopy) Fields() []string {
	if d.isReleased {
		return []string{}
	}
	return d.dataset.Fields()
}

// NumRecords returns the number of records in the Dataset.
func (d *DCopy) NumRecords() int64 {
	return d.numRecords
}

// Release releases any resources associated with the Dataset d,
// rendering it unusable in the future.  In this case it deletes
// the temporary copy of the Dataset.
func (d *DCopy) Release() error {
	if !d.isReleased {
		err := os.RemoveAll(d.tmpDir)
		if err == nil {
			d.isReleased = true
		}
		return err
	}
	return ddataset.ErrReleased
}

// Next returns whether there is a Record to be Read
func (c *DCopyConn) Next() bool {
	return c.conn.Next()
}

// Err returns any errors from the connection
func (c *DCopyConn) Err() error {
	return c.conn.Err()
}

// Read returns the current Record
func (c *DCopyConn) Read() ddataset.Record {
	return c.conn.Read()
}

// Close closes the connection and deletes the copy
func (c *DCopyConn) Close() error {
	return c.conn.Close()
}
<|endoftext|>


<commit_before>package handlers

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"

	"github.com/franela/play-with-docker/config"
	"github.com/gorilla/mux"
)

func getTargetInfo(vars map[string]string, req *http.Request) (string, string) {
	node := vars["node"]
	port := vars["port"]
	hostPort := strings.Split(req.Host, ":")

	// give priority to the URL host port
	if len(hostPort) > 1 && hostPort[1] != config.PortNumber {
		port = hostPort[1]
	} else if port == "" {
		port = "80"
	}

	if strings.HasPrefix(node, "pwd") {
		// Node is actually an ip, need to convert underscores by dots.
		ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1)

		if net.ParseIP(ip) == nil {
			// Not a valid IP, so treat this is a hostname.
		} else {
			node = ip
		}
	}

	return node, port

}

func NewMultipleHostReverseProxy() *httputil.ReverseProxy {
	var transport http.RoundTripper = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 0,
		}).DialContext,
		DisableKeepAlives:     true,
		MaxIdleConns:          1,
		IdleConnTimeout:       100 * time.Millisecond,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
	}
	director := func(req *http.Request) {
		v := mux.Vars(req)
		node, port := getTargetInfo(v, req)

		if port == "443" {
			// Only proxy http for now
			req.URL.Scheme = "https"
		} else {
			// Only proxy http for now
			req.URL.Scheme = "http"
		}

		req.URL.Host = fmt.Sprintf("%s:%s", node, port)
	}

	return &httputil.ReverseProxy{Director: director, Transport: transport}
}

func NewSSLDaemonHandler() *httputil.ReverseProxy {
	director := func(req *http.Request) {
		v := mux.Vars(req)
		node := v["node"]
		if strings.HasPrefix(node, "pwd") {
			// Node is actually an ip, need to convert underscores by dots.
			ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1)

			if net.ParseIP(ip) == nil {
				// Not a valid IP, so treat this is a hostname.
			} else {
				node = ip
			}
		}

		// Only proxy http for now
		req.URL.Scheme = "http"

		req.URL.Host = fmt.Sprintf("%s:%s", node, "2375")
	}

	return &httputil.ReverseProxy{Director: director}
}
<commit_msg>Add some logging to the proxy<commit_after>package handlers

import (
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"

	"github.com/franela/play-with-docker/config"
	"github.com/gorilla/mux"
)

func getTargetInfo(vars map[string]string, req *http.Request) (string, string) {
	node := vars["node"]
	port := vars["port"]
	hostPort := strings.Split(req.Host, ":")

	// give priority to the URL host port
	if len(hostPort) > 1 && hostPort[1] != config.PortNumber {
		port = hostPort[1]
	} else if port == "" {
		port = "80"
	}

	if strings.HasPrefix(node, "pwd") {
		// Node is actually an ip, need to convert underscores by dots.
		ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1)

		if net.ParseIP(ip) == nil {
			// Not a valid IP, so treat this is a hostname.
		} else {
			node = ip
		}
	}

	return node, port

}

func NewMultipleHostReverseProxy() *httputil.ReverseProxy {
	var transport http.RoundTripper = &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 0,
		}).DialContext,
		DisableKeepAlives:     true,
		MaxIdleConns:          1,
		IdleConnTimeout:       100 * time.Millisecond,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
	}
	director := func(req *http.Request) {
		v := mux.Vars(req)
		node, port := getTargetInfo(v, req)

		if port == "443" {
			// Only proxy http for now
			req.URL.Scheme = "https"
		} else {
			// Only proxy http for now
			req.URL.Scheme = "http"
		}

		req.URL.Host = fmt.Sprintf("%s:%s", node, port)
		log.Printf("HTTP Reverse proxying to %s\n", req.URL.Host)
	}

	return &httputil.ReverseProxy{Director: director, Transport: transport}
}

func NewSSLDaemonHandler() *httputil.ReverseProxy {
	director := func(req *http.Request) {
		v := mux.Vars(req)
		node := v["node"]
		if strings.HasPrefix(node, "pwd") {
			// Node is actually an ip, need to convert underscores by dots.
			ip := strings.Replace(strings.TrimPrefix(node, "pwd"), "_", ".", -1)

			if net.ParseIP(ip) == nil {
				// Not a valid IP, so treat this is a hostname.
			} else {
				node = ip
			}
		}

		// Only proxy http for now
		req.URL.Scheme = "http"

		req.URL.Host = fmt.Sprintf("%s:%s", node, "2375")
		log.Printf("HTTPS Reverse proxying to %s\n", req.URL.Host)
	}

	return &httputil.ReverseProxy{Director: director}
}
<|endoftext|>


<commit_before><commit_msg>Update import.<commit_after><|endoftext|>


<commit_before>package sortedmap

import (
	"testing"
	"time"
)

func TestDelete(t *testing.T) {
	sm, records, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}
	if sm.Delete("") {
		t.Fatalf("Delete: %v", invalidDelete)
	}
	for _, rec := range records {
		sm.Delete(rec.Key)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}

func TestBatchDelete(t *testing.T) {
	sm, records, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}

	keys := make([]interface{}, 0)
	for i, rec := range records {
		if i == 50 {
			break
		}
		keys = append(keys, rec.Key)
	}

	for _, ok := range sm.BatchDelete(keys) {
		if !ok {
			t.Fatalf("BatchDelete: %v", invalidDelete)
		}
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}

func TestDeleteBetween(t *testing.T) {
	const (
		nilBoundValsErr = "accepted nil bound value"
		generalBoundsErr = "general bounds error"
	)

	sm, _, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}

	earlierDate := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)

	if !sm.DeleteBetween(nil, nil) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(nil, time.Now()) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(time.Now(), nil) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(earlierDate, time.Now()) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(time.Now(), earlierDate) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(earlierDate, earlierDate) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}<commit_msg>updated DeleteBetween test: fixed typos<commit_after>package sortedmap

import (
	"testing"
	"time"
)

func TestDelete(t *testing.T) {
	sm, records, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}
	if sm.Delete("") {
		t.Fatalf("Delete: %v", invalidDelete)
	}
	for _, rec := range records {
		sm.Delete(rec.Key)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}

func TestBatchDelete(t *testing.T) {
	sm, records, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}

	keys := make([]interface{}, 0)
	for i, rec := range records {
		if i == 50 {
			break
		}
		keys = append(keys, rec.Key)
	}

	for _, ok := range sm.BatchDelete(keys) {
		if !ok {
			t.Fatalf("BatchDelete: %v", invalidDelete)
		}
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}

func TestDeleteBetween(t *testing.T) {
	const (
		nilBoundValsErr = "accepted nil bound value"
		generalBoundsErr = "general bounds error"
	)

	sm, _, err := newSortedMapFromRandRecords(300)
	if err != nil {
		t.Fatal(err)
	}

	earlierDate := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)

	if sm.DeleteBetween(nil, nil) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}

	if sm.DeleteBetween(nil, time.Now()) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}

	if sm.DeleteBetween(time.Now(), nil) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(earlierDate, time.Now()) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(time.Now(), earlierDate) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}

	if !sm.DeleteBetween(earlierDate, earlierDate) {
		t.Fatalf("TestDeleteBetween failed: %v", generalBoundsErr)
	}
	if err := verifyRecords(sm.IterCh(), false); err != nil {
		t.Fatal(err)
	}
}<|endoftext|>


<commit_before>// Copyright (c) 2017 Johan Brandhorst

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package jspb

// JspbPackageIsVersion1 is referenced from generated protocol buffer files
// to assert that that code is compatible with this version of the package.
const JspbPackageIsVersion1 = true
<commit_msg>Bump jspb package version assertion<commit_after>// Copyright (c) 2017 Johan Brandhorst

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package jspb

// JspbPackageIsVersion2 is referenced from generated protocol buffer files
// to assert that that code is compatible with this version of the package.
const JspbPackageIsVersion2 = true
<|endoftext|>


<commit_before><commit_msg>Fix S3 stat 404<commit_after><|endoftext|>


<commit_before>package kcp_test

import (
	"testing"

	"github.com/v2ray/v2ray-core/testing/assert"
	. "github.com/v2ray/v2ray-core/transport/internet/kcp"
)

func TestSendingQueue(t *testing.T) {
	assert := assert.On(t)

	queue := NewSendingQueue(3)

	seg0 := &DataSegment{}
	seg1 := &DataSegment{}
	seg2 := &DataSegment{}
	seg3 := &DataSegment{}

	assert.Bool(queue.IsEmpty()).IsTrue()
	assert.Bool(queue.IsFull()).IsFalse()

	queue.Push(seg0)
	assert.Bool(queue.IsEmpty()).IsFalse()

	queue.Push(seg1)
	queue.Push(seg2)

	assert.Bool(queue.IsFull()).IsTrue()

	assert.Pointer(queue.Pop()).Equals(seg0)

	queue.Push(seg3)
	assert.Bool(queue.IsFull()).IsTrue()

	assert.Pointer(queue.Pop()).Equals(seg1)
	assert.Pointer(queue.Pop()).Equals(seg2)
	assert.Pointer(queue.Pop()).Equals(seg3)
	assert.Int(int(queue.Len())).Equals(0)
}

func TestSendingQueueClear(t *testing.T) {
	assert := assert.On(t)

	queue := NewSendingQueue(3)

	seg0 := &DataSegment{}
	seg1 := &DataSegment{}
	seg2 := &DataSegment{}
	seg3 := &DataSegment{}

	queue.Push(seg0)
	assert.Bool(queue.IsEmpty()).IsFalse()

	queue.Clear()
	assert.Bool(queue.IsEmpty()).IsTrue()

	queue.Push(seg1)
	queue.Push(seg2)
	queue.Push(seg3)

	queue.Clear()
	assert.Bool(queue.IsEmpty()).IsTrue()
}

func TestSendingWindow(t *testing.T) {
	assert := assert.On(t)

	window := NewSendingWindow(nil, 5)
	window.Push(&DataSegment{
		Number: 0,
	})
	window.Push(&DataSegment{
		Number: 1,
	})
	window.Push(&DataSegment{
		Number: 2,
	})
	assert.Int(window.Len()).Equals(3)

	window.Remove(1)
	assert.Int(window.Len()).Equals(3)
	assert.Uint32(window.First().Number).Equals(0)

	window.Remove(0)
	assert.Int(window.Len()).Equals(1)
	assert.Uint32(window.First().Number).Equals(2)

	window.Remove(0)
	assert.Int(window.Len()).Equals(0)

	window.Push(&DataSegment{
		Number: 4,
	})
	assert.Int(window.Len()).Equals(1)
	assert.Uint32(window.First().Number).Equals(4)

	window.Push(&DataSegment{
		Number: 5,
	})
	assert.Int(window.Len()).Equals(2)

	window.Remove(1)
	assert.Int(window.Len()).Equals(2)

	window.Remove(0)
	assert.Int(window.Len()).Equals(0)
}
<commit_msg>fix test break<commit_after>package kcp_test

import (
	"testing"

	"github.com/v2ray/v2ray-core/testing/assert"
	. "github.com/v2ray/v2ray-core/transport/internet/kcp"
)

func TestSendingQueue(t *testing.T) {
	assert := assert.On(t)

	queue := NewSendingQueue(3)

	seg0 := &DataSegment{}
	seg1 := &DataSegment{}
	seg2 := &DataSegment{}
	seg3 := &DataSegment{}

	assert.Bool(queue.IsEmpty()).IsTrue()
	assert.Bool(queue.IsFull()).IsFalse()

	queue.Push(seg0)
	assert.Bool(queue.IsEmpty()).IsFalse()

	queue.Push(seg1)
	queue.Push(seg2)

	assert.Bool(queue.IsFull()).IsTrue()

	assert.Pointer(queue.Pop()).Equals(seg0)

	queue.Push(seg3)
	assert.Bool(queue.IsFull()).IsTrue()

	assert.Pointer(queue.Pop()).Equals(seg1)
	assert.Pointer(queue.Pop()).Equals(seg2)
	assert.Pointer(queue.Pop()).Equals(seg3)
	assert.Int(int(queue.Len())).Equals(0)
}

func TestSendingQueueClear(t *testing.T) {
	assert := assert.On(t)

	queue := NewSendingQueue(3)

	seg0 := &DataSegment{}
	seg1 := &DataSegment{}
	seg2 := &DataSegment{}
	seg3 := &DataSegment{}

	queue.Push(seg0)
	assert.Bool(queue.IsEmpty()).IsFalse()

	queue.Clear()
	assert.Bool(queue.IsEmpty()).IsTrue()

	queue.Push(seg1)
	queue.Push(seg2)
	queue.Push(seg3)

	queue.Clear()
	assert.Bool(queue.IsEmpty()).IsTrue()
}

func TestSendingWindow(t *testing.T) {
	assert := assert.On(t)

	window := NewSendingWindow(5, nil, nil)
	window.Push(&DataSegment{
		Number: 0,
	})
	window.Push(&DataSegment{
		Number: 1,
	})
	window.Push(&DataSegment{
		Number: 2,
	})
	assert.Int(window.Len()).Equals(3)

	window.Remove(1)
	assert.Int(window.Len()).Equals(3)
	assert.Uint32(window.First().Number).Equals(0)

	window.Remove(0)
	assert.Int(window.Len()).Equals(1)
	assert.Uint32(window.First().Number).Equals(2)

	window.Remove(0)
	assert.Int(window.Len()).Equals(0)

	window.Push(&DataSegment{
		Number: 4,
	})
	assert.Int(window.Len()).Equals(1)
	assert.Uint32(window.First().Number).Equals(4)

	window.Push(&DataSegment{
		Number: 5,
	})
	assert.Int(window.Len()).Equals(2)

	window.Remove(1)
	assert.Int(window.Len()).Equals(2)

	window.Remove(0)
	assert.Int(window.Len()).Equals(0)
}
<|endoftext|>