<commit_before>/*
Copyright 2011 Google 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 localdisk registers the "filesystem" blobserver storage type,
storing blobs in a forest of sharded directories at the specified root.

Example low-level config:

     "/storage/": {
         "handler": "storage-filesystem",
         "handlerArgs": {
            "path": "/var/camlistore/blobs"
          }
     },

*/
package localdisk

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/local"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/osutil"
	"camlistore.org/pkg/types"
)

// DiskStorage implements the blobserver.Storage interface using the
// local filesystem.
type DiskStorage struct {
	root string

	// dirLockMu must be held for writing when deleting an empty directory
	// and for read when receiving blobs.
	dirLockMu *sync.RWMutex

	// gen will be nil if partition != ""
	gen *local.Generationer
}

func (ds *DiskStorage) String() string {
	return fmt.Sprintf("\"filesystem\" file-per-blob at %s", ds.root)
}

// IsDir reports whether root is a localdisk (file-per-blob) storage directory.
func IsDir(root string) (bool, error) {
	if osutil.DirExists(filepath.Join(root, blob.RefFromString("").HashName())) {
		return true, nil
	}
	return false, nil
}

// New returns a new local disk storage implementation at the provided
// root directory, which must already exist.
func New(root string) (*DiskStorage, error) {
	// Local disk.
	fi, err := os.Stat(root)
	if os.IsNotExist(err) {
		return nil, fmt.Errorf("Storage root %q doesn't exist", root)
	}
	if err != nil {
		return nil, fmt.Errorf("Failed to stat directory %q: %v", root, err)
	}
	if !fi.IsDir() {
		return nil, fmt.Errorf("Storage root %q exists but is not a directory.", root)
	}
	ds := &DiskStorage{
		root:      root,
		dirLockMu: new(sync.RWMutex),
		gen:       local.NewGenerationer(root),
	}
	if err := ds.migrate3to2(); err != nil {
		return nil, fmt.Errorf("Error updating localdisk format: %v", err)
	}
	if _, _, err := ds.StorageGeneration(); err != nil {
		return nil, fmt.Errorf("Error initialization generation for %q: %v", root, err)
	}
	return ds, nil
}

func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
	path := config.RequiredString("path")
	if err := config.Validate(); err != nil {
		return nil, err
	}
	return New(path)
}

func init() {
	blobserver.RegisterStorageConstructor("filesystem", blobserver.StorageConstructor(newFromConfig))
}

func (ds *DiskStorage) tryRemoveDir(dir string) {
	ds.dirLockMu.Lock()
	defer ds.dirLockMu.Unlock()
	os.Remove(dir) // ignore error
}

func (ds *DiskStorage) Fetch(br blob.Ref) (io.ReadCloser, uint32, error) {
	return ds.fetch(br, 0, -1)
}

func (ds *DiskStorage) SubFetch(br blob.Ref, offset, length int64) (io.ReadCloser, error) {
	if offset < 0 || length < 0 {
		return nil, errors.New("invalid offset or length")
	}
	rc, _, err := ds.fetch(br, offset, length)
	return rc, err
}

// length -1 means entire file
func (ds *DiskStorage) fetch(br blob.Ref, offset, length int64) (rc io.ReadCloser, size uint32, err error) {
	fileName := ds.blobPath(br)
	stat, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		return nil, 0, os.ErrNotExist
	}
	size = types.U32(stat.Size())
	file, err := os.Open(fileName)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.ErrNotExist
		}
		return nil, 0, err
	}
	// normal Fetch:
	if length < 0 {
		return file, size, nil
	}
	// SubFetch:
	if offset < 0 || offset > stat.Size() {
		return nil, 0, errors.New("invalid offset")
	}
	return struct {
		io.Reader
		io.Closer
	}{
		io.NewSectionReader(file, offset, length),
		file,
	}, 0 /* unused */, err
}

func (ds *DiskStorage) RemoveBlobs(blobs []blob.Ref) error {
	for _, blob := range blobs {
		fileName := ds.blobPath(blob)
		err := os.Remove(fileName)
		switch {
		case err == nil:
			continue
		case os.IsNotExist(err):
			// deleting already-deleted file; harmless.
			continue
		default:
			return err
		}
	}
	return nil
}
<commit_msg>localdisk: auto-create packed directory on start-up<commit_after>/*
Copyright 2011 Google 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 localdisk registers the "filesystem" blobserver storage type,
storing blobs in a forest of sharded directories at the specified root.

Example low-level config:

     "/storage/": {
         "handler": "storage-filesystem",
         "handlerArgs": {
            "path": "/var/camlistore/blobs"
          }
     },

*/
package localdisk

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/local"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/osutil"
	"camlistore.org/pkg/types"
)

// DiskStorage implements the blobserver.Storage interface using the
// local filesystem.
type DiskStorage struct {
	root string

	// dirLockMu must be held for writing when deleting an empty directory
	// and for read when receiving blobs.
	dirLockMu *sync.RWMutex

	// gen will be nil if partition != ""
	gen *local.Generationer
}

func (ds *DiskStorage) String() string {
	return fmt.Sprintf("\"filesystem\" file-per-blob at %s", ds.root)
}

// IsDir reports whether root is a localdisk (file-per-blob) storage directory.
func IsDir(root string) (bool, error) {
	if osutil.DirExists(filepath.Join(root, blob.RefFromString("").HashName())) {
		return true, nil
	}
	return false, nil
}

// New returns a new local disk storage implementation at the provided
// root directory, which must already exist.
func New(root string) (*DiskStorage, error) {
	// Local disk.
	fi, err := os.Stat(root)
	if os.IsNotExist(err) {
		// As a special case, we auto-created the "packed" directory for subpacked.
		if filepath.Base(root) == "packed" {
			if err := os.Mkdir(root, 0700); err != nil {
				return nil, fmt.Errorf("failed to mkdir packed directory: %v", err)
			}
			fi, err = os.Stat(root)
		} else {
			return nil, fmt.Errorf("Storage root %q doesn't exist", root)
		}
	}
	if err != nil {
		return nil, fmt.Errorf("Failed to stat directory %q: %v", root, err)
	}
	if !fi.IsDir() {
		return nil, fmt.Errorf("Storage root %q exists but is not a directory.", root)
	}
	ds := &DiskStorage{
		root:      root,
		dirLockMu: new(sync.RWMutex),
		gen:       local.NewGenerationer(root),
	}
	if err := ds.migrate3to2(); err != nil {
		return nil, fmt.Errorf("Error updating localdisk format: %v", err)
	}
	if _, _, err := ds.StorageGeneration(); err != nil {
		return nil, fmt.Errorf("Error initialization generation for %q: %v", root, err)
	}
	return ds, nil
}

func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
	path := config.RequiredString("path")
	if err := config.Validate(); err != nil {
		return nil, err
	}
	return New(path)
}

func init() {
	blobserver.RegisterStorageConstructor("filesystem", blobserver.StorageConstructor(newFromConfig))
}

func (ds *DiskStorage) tryRemoveDir(dir string) {
	ds.dirLockMu.Lock()
	defer ds.dirLockMu.Unlock()
	os.Remove(dir) // ignore error
}

func (ds *DiskStorage) Fetch(br blob.Ref) (io.ReadCloser, uint32, error) {
	return ds.fetch(br, 0, -1)
}

func (ds *DiskStorage) SubFetch(br blob.Ref, offset, length int64) (io.ReadCloser, error) {
	if offset < 0 || length < 0 {
		return nil, errors.New("invalid offset or length")
	}
	rc, _, err := ds.fetch(br, offset, length)
	return rc, err
}

// length -1 means entire file
func (ds *DiskStorage) fetch(br blob.Ref, offset, length int64) (rc io.ReadCloser, size uint32, err error) {
	fileName := ds.blobPath(br)
	stat, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		return nil, 0, os.ErrNotExist
	}
	size = types.U32(stat.Size())
	file, err := os.Open(fileName)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.ErrNotExist
		}
		return nil, 0, err
	}
	// normal Fetch:
	if length < 0 {
		return file, size, nil
	}
	// SubFetch:
	if offset < 0 || offset > stat.Size() {
		return nil, 0, errors.New("invalid offset")
	}
	return struct {
		io.Reader
		io.Closer
	}{
		io.NewSectionReader(file, offset, length),
		file,
	}, 0 /* unused */, err
}

func (ds *DiskStorage) RemoveBlobs(blobs []blob.Ref) error {
	for _, blob := range blobs {
		fileName := ds.blobPath(blob)
		err := os.Remove(fileName)
		switch {
		case err == nil:
			continue
		case os.IsNotExist(err):
			// deleting already-deleted file; harmless.
			continue
		default:
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package errors

import (
	"fmt"
	"os/exec"
	"runtime"
)

// ErrNoDockerClient is thrown when a Docker client cannot be obtained or cannot be pinged
func ErrNoDockerClient(err error) error {
	return NewError("cannot obtain a Docker client").WithCause(err).WithSolution(noDockerClientSolution())
}

func ErrNoDockerMachineClient(name string, err error) error {
	return NewError("cannot obtain a client for Docker machine %q", name).WithCause(err).WithSolution(noDockerMachineClientSolution())
}

func ErrCannotPingDocker(err error) error {
	return NewError("cannot communicate with Docker").WithCause(err).WithSolution(noDockerClientSolution())
}

// ErrNoInsecureRegistryArgument is thrown when an --insecure-registry argument cannot be detected
// on the Docker daemon process
func ErrNoInsecureRegistryArgument() error {
	return NewError("did not detect an --insecure-registry argument on the Docker daemon").WithSolution(noInsecureRegistryArgSolution())
}

const (
	NoDockerMacSolution = `
Please install Docker tools by following instructions at:

   https://docs.docker.com/mac/

Once installed, run this command with the --create-machine
argument to create a new Docker machine that will run OpenShift.
`
	NoDockerMachineMacSolution = `
To create a new Docker machine to run OpenShift, run this command again with
the --create-machine argument. This will create a Docker machine named
'openshift'.

To use a different machine name, specify the --machine-name=NAME argument.

If you wish to use an existing Docker machine, enable it before running this
command by executing:

   eval $(docker-machine env NAME)

where NAME is the name of your Docker machine.
`
	NoDockerWindowsSolution = `
Please install Docker tools by following instructions at:

   https://docs.docker.com/windows/

Once installed, run this command with the --create-machine argument to create a
new Docker machine that will run OpenShift.
`
	NoDockerMachineWindowsSolution = `
To create a new Docker machine to run OpenShift, run this command again with
the --create-machine argument. This will create a Docker machine named
'openshift'.

To use a different machine name, specify the --machine-name=NAME argument.

If you wish to use an existing Docker machine, enable it before running this
command by executing:

   docker-machine env

where NAME is the name of your Docker machine.
`
	NoDockerLinuxSolution = `
Ensure that Docker is installed and accessible in your environment.
Use your package manager or follow instructions at:

   https://docs.docker.com/linux/
`

	NoDockerMachineClientSolution = `
Ensure that the Docker machine is available and running. You can also create a
new Docker machine by specifying the --create-machine flag.
`

	NoInsecureRegistryArgSolution = `
Ensure that the Docker daemon is running with the following argument:
	--insecure-registry "172.30.0.0/16"
`

	NoInsecureRegistryArgSolutionDockerMachine = NoInsecureRegistryArgSolution + `
You can run this command with --create-machine to create a machine with the
right argument.
`
)

func hasDockerMachine() bool {
	binary := "docker-machine"
	if runtime.GOOS == "windows" {
		binary += ".exe"
	}
	_, err := exec.LookPath(binary)
	return err == nil
}

func noDockerClientSolution() string {
	switch runtime.GOOS {
	case "darwin":
		if hasDockerMachine() {
			return NoDockerMachineMacSolution
		}
		return NoDockerMacSolution
	case "windows":
		if hasDockerMachine() {
			return NoDockerMachineWindowsSolution
		}
		return NoDockerWindowsSolution
	case "linux":
		return NoDockerLinuxSolution
	}
	return fmt.Sprintf("Platform %s is not supported by this command", runtime.GOOS)
}

func noDockerMachineClientSolution() string {
	return NoDockerMachineClientSolution
}

func noInsecureRegistryArgSolution() string {
	switch runtime.GOOS {
	case "darwin":
		if hasDockerMachine() {
			return NoInsecureRegistryArgSolutionDockerMachine
		}
	case "windows":
		if hasDockerMachine() {
			return NoInsecureRegistryArgSolutionDockerMachine
		}
	}
	return NoInsecureRegistryArgSolution
}
<commit_msg>cluster up: fix insecure registry argument message<commit_after>package errors

import (
	"fmt"
	"os/exec"
	"runtime"
)

// ErrNoDockerClient is thrown when a Docker client cannot be obtained or cannot be pinged
func ErrNoDockerClient(err error) error {
	return NewError("cannot obtain a Docker client").WithCause(err).WithSolution(noDockerClientSolution())
}

func ErrNoDockerMachineClient(name string, err error) error {
	return NewError("cannot obtain a client for Docker machine %q", name).WithCause(err).WithSolution(noDockerMachineClientSolution())
}

func ErrCannotPingDocker(err error) error {
	return NewError("cannot communicate with Docker").WithCause(err).WithSolution(noDockerClientSolution())
}

// ErrNoInsecureRegistryArgument is thrown when an --insecure-registry argument cannot be detected
// on the Docker daemon process
func ErrNoInsecureRegistryArgument() error {
	return NewError("did not detect an --insecure-registry argument on the Docker daemon").WithSolution(noInsecureRegistryArgSolution())
}

const (
	NoDockerMacSolution = `
Please install Docker tools by following instructions at:

   https://docs.docker.com/mac/

Once installed, run this command with the --create-machine
argument to create a new Docker machine that will run OpenShift.
`
	NoDockerMachineMacSolution = `
To create a new Docker machine to run OpenShift, run this command again with
the --create-machine argument. This will create a Docker machine named
'openshift'.

To use a different machine name, specify the --machine-name=NAME argument.

If you wish to use an existing Docker machine, enable it before running this
command by executing:

   eval $(docker-machine env NAME)

where NAME is the name of your Docker machine.
`
	NoDockerWindowsSolution = `
Please install Docker tools by following instructions at:

   https://docs.docker.com/windows/

Once installed, run this command with the --create-machine argument to create a
new Docker machine that will run OpenShift.
`
	NoDockerMachineWindowsSolution = `
To create a new Docker machine to run OpenShift, run this command again with
the --create-machine argument. This will create a Docker machine named
'openshift'.

To use a different machine name, specify the --machine-name=NAME argument.

If you wish to use an existing Docker machine, enable it before running this
command by executing:

   docker-machine env

where NAME is the name of your Docker machine.
`
	NoDockerLinuxSolution = `
Ensure that Docker is installed and accessible in your environment.
Use your package manager or follow instructions at:

   https://docs.docker.com/linux/
`

	NoDockerMachineClientSolution = `
Ensure that the Docker machine is available and running. You can also create a
new Docker machine by specifying the --create-machine flag.
`

	NoInsecureRegistryArgSolution = `
Ensure that the Docker daemon is running with the following argument:
	--insecure-registry 172.30.0.0/16
`

	NoInsecureRegistryArgSolutionDockerMachine = NoInsecureRegistryArgSolution + `
You can run this command with --create-machine to create a machine with the
right argument.
`
)

func hasDockerMachine() bool {
	binary := "docker-machine"
	if runtime.GOOS == "windows" {
		binary += ".exe"
	}
	_, err := exec.LookPath(binary)
	return err == nil
}

func noDockerClientSolution() string {
	switch runtime.GOOS {
	case "darwin":
		if hasDockerMachine() {
			return NoDockerMachineMacSolution
		}
		return NoDockerMacSolution
	case "windows":
		if hasDockerMachine() {
			return NoDockerMachineWindowsSolution
		}
		return NoDockerWindowsSolution
	case "linux":
		return NoDockerLinuxSolution
	}
	return fmt.Sprintf("Platform %s is not supported by this command", runtime.GOOS)
}

func noDockerMachineClientSolution() string {
	return NoDockerMachineClientSolution
}

func noInsecureRegistryArgSolution() string {
	switch runtime.GOOS {
	case "darwin":
		if hasDockerMachine() {
			return NoInsecureRegistryArgSolutionDockerMachine
		}
	case "windows":
		if hasDockerMachine() {
			return NoInsecureRegistryArgSolutionDockerMachine
		}
	}
	return NoInsecureRegistryArgSolution
}
<|endoftext|>


<commit_before>// Copyright 2018 Authors of Cilium
//
// 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 !privileged_tests

package regexpmap

import (
	"fmt"
	"regexp"
	"strings"
	"testing"

	. "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
	TestingT(t)
}

type RegexpMapTestSuite struct{}

var _ = Suite(&RegexpMapTestSuite{})

func (ds *RegexpMapTestSuite) TestRegexList(c *C) {
	m := NewRegexpList(".*", "foo.bar.", "www.*.com")

	res := m.Get()
	c.Assert(res, HasLen, 3)
	c.Assert(res[0], Equals, ".*")
	c.Assert(res[2], Equals, "www.*.com")

	m.Remove(".*")
	c.Assert(m.Get(), HasLen, 2)

	m.Remove(".*")
	c.Assert(m.Get(), HasLen, 2)
}

func (ds *RegexpMapTestSuite) TestRefCountDecreasesCorrectly(c *C) {
	m := NewRegexpMap()
	m.Add("cilium.io", "ID1")
	m.Add("cilium.io", "ID1")
	m.Add("cilium.io", "ID3")

	c.Assert(m.Remove("cilium.io", "ID1"), Equals, false)
	c.Assert(m.LookupValues("cilium.io"), HasLen, 2)
	c.Assert(m.LookupContainsValue("cilium.io", "ID1"), Equals, true)

	c.Assert(m.Remove("cilium.io", "ID1"), Equals, false)
	c.Assert(m.LookupValues("cilium.io"), HasLen, 1)

	c.Assert(m.Remove("cilium.io", "ID3"), Equals, true)
}

func (ds *RegexpMapTestSuite) TestREMapInsertLookup(c *C) {
	// Can we compile and add entries
	m := NewRegexpMap()
	err := m.Add("foo.bar.com.", "ID1")
	c.Assert(err, IsNil, Commentf("Error compiling regex in map"))
	err = m.Add("foo.bar.com.", "ID2")
	c.Assert(err, IsNil, Commentf("Error compiling regex in map"))

	// Do we return the same entries we inserted
	keys := m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 2, Commentf("Incorrect number of values returned %v", keys))
	c.Assert(keys[0], Equals, "ID1", Commentf("Incorrect value returned"))
	c.Assert(keys[1], Equals, "ID2", Commentf("Incorrect value returned"))

	// Do we return nothing on no match
	keys = m.LookupValues("notabar.com.")
	c.Assert(len(keys), Equals, 0, Commentf("Returned values for non-match key %v", keys))

	// Does LookupValue match Added values internally?
	match := m.LookupContainsValue("foo.bar.com.", "ID1")

	c.Assert(match, Equals, true, Commentf("No match for key & value that should match"))
	match = m.LookupContainsValue("foo.bar.com.", "ID2")
	c.Assert(match, Equals, true, Commentf("No match for key & value that should match"))
	match = m.LookupContainsValue("foo.bar.com.", "ID3")
	c.Assert(match, Equals, false, Commentf("Match for key that matches & value that does not match"))
	match = m.LookupContainsValue("notabar.com.", "ID2")
	c.Assert(match, Equals, false, Commentf("Match for key that does not matche & value that does match"))
	// Does removing an entry also remove it from lookup returns

	m.Remove("foo.bar.com.", "ID1")
	keys = m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 1, Commentf("Incorrect number of values returned %v", keys))
	c.Assert(keys[0], Equals, "ID2", Commentf("Incorrect value returned"))

	// Do we crash on double removes, and will the map be empty after all keys are removed.
	m.Remove("foo.bar.com.", "ID1") // a no-op
	m.Remove("foo.bar.com.", "ID2")
	m.Remove("foo.bar.com.", "ID2") // a no-op
	keys = m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 0, Commentf("Incorrect number of values returned %v", keys))
}

func (ds *RegexpMapTestSuite) TestKeepUniqueStrings(c *C) {
	in := []string{"ID1", "ID2", "ID2"}
	out := keepUniqueStrings(in)
	for i, elem := range in[:2] {
		c.Assert(elem, Equals, out[i])
	}
}

//  reSize is the number of distinct subpatterns/regexes to benchmark with
var reSize = 100

func (ds *RegexpMapTestSuite) BenchmarkRegexSingle(c *C) {
	c.StopTimer()
	re := regexp.MustCompile("(?P<iter_>bar.foo.com.)")
	in := []byte("bar.foo.com.")
	c.StartTimer()
	for i := c.N; i > 0; i-- {
		re.FindSubmatchIndex(in)
	}
}

func (ds *RegexpMapTestSuite) BenchmarkRegexLinearSearch(c *C) {
	c.StopTimer()
	m := map[*regexp.Regexp]int{}
	for i := reSize; i > 0; i-- {
		m[regexp.MustCompile("bar.foo.com.")] = i
	}
	in := []byte("bar.foo.com.")
	c.StartTimer()
	for i := c.N; i > 0; i-- {
		for re := range m {
			re.FindSubmatchIndex(in)
		}
	}
}

func (ds *RegexpMapTestSuite) BenchmarkRegexGroups(c *C) {
	c.StopTimer()
	elements := []string{}
	var re *regexp.Regexp
	for i := reSize; i > 0; i-- {
		elements = append(elements, fmt.Sprintf("(?P<iter_%d>bar.foo.com.)", i))
	}
	re = regexp.MustCompile("^" + strings.Join(elements, "|") + "$")

	in := []byte("bar.foo.com.")
	c.StartTimer()

	for i := c.N; i > 0; i-- {
		re.FindSubmatchIndex(in)
	}
}
<commit_msg>FQDN: Add regexMap benchmark tests.<commit_after>// Copyright 2018 Authors of Cilium
//
// 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 !privileged_tests

package regexpmap

import (
	"fmt"
	"regexp"
	"strings"
	"testing"

	. "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
	TestingT(t)
}

type RegexpMapTestSuite struct{}

var _ = Suite(&RegexpMapTestSuite{})

func (ds *RegexpMapTestSuite) TestRegexList(c *C) {
	m := NewRegexpList(".*", "foo.bar.", "www.*.com")

	res := m.Get()
	c.Assert(res, HasLen, 3)
	c.Assert(res[0], Equals, ".*")
	c.Assert(res[2], Equals, "www.*.com")

	m.Remove(".*")
	c.Assert(m.Get(), HasLen, 2)

	m.Remove(".*")
	c.Assert(m.Get(), HasLen, 2)
}

func (ds *RegexpMapTestSuite) TestRefCountDecreasesCorrectly(c *C) {
	m := NewRegexpMap()
	m.Add("cilium.io", "ID1")
	m.Add("cilium.io", "ID1")
	m.Add("cilium.io", "ID3")

	c.Assert(m.Remove("cilium.io", "ID1"), Equals, false)
	c.Assert(m.LookupValues("cilium.io"), HasLen, 2)
	c.Assert(m.LookupContainsValue("cilium.io", "ID1"), Equals, true)

	c.Assert(m.Remove("cilium.io", "ID1"), Equals, false)
	c.Assert(m.LookupValues("cilium.io"), HasLen, 1)

	c.Assert(m.Remove("cilium.io", "ID3"), Equals, true)
}

func (ds *RegexpMapTestSuite) TestREMapInsertLookup(c *C) {
	// Can we compile and add entries
	m := NewRegexpMap()
	err := m.Add("foo.bar.com.", "ID1")
	c.Assert(err, IsNil, Commentf("Error compiling regex in map"))
	err = m.Add("foo.bar.com.", "ID2")
	c.Assert(err, IsNil, Commentf("Error compiling regex in map"))

	// Do we return the same entries we inserted
	keys := m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 2, Commentf("Incorrect number of values returned %v", keys))
	c.Assert(keys[0], Equals, "ID1", Commentf("Incorrect value returned"))
	c.Assert(keys[1], Equals, "ID2", Commentf("Incorrect value returned"))

	// Do we return nothing on no match
	keys = m.LookupValues("notabar.com.")
	c.Assert(len(keys), Equals, 0, Commentf("Returned values for non-match key %v", keys))

	// Does LookupValue match Added values internally?
	match := m.LookupContainsValue("foo.bar.com.", "ID1")

	c.Assert(match, Equals, true, Commentf("No match for key & value that should match"))
	match = m.LookupContainsValue("foo.bar.com.", "ID2")
	c.Assert(match, Equals, true, Commentf("No match for key & value that should match"))
	match = m.LookupContainsValue("foo.bar.com.", "ID3")
	c.Assert(match, Equals, false, Commentf("Match for key that matches & value that does not match"))
	match = m.LookupContainsValue("notabar.com.", "ID2")
	c.Assert(match, Equals, false, Commentf("Match for key that does not matche & value that does match"))

	// Does removing an entry also remove it from lookup returns
	m.Remove("foo.bar.com.", "ID1")
	keys = m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 1, Commentf("Incorrect number of values returned %v", keys))
	c.Assert(keys[0], Equals, "ID2", Commentf("Incorrect value returned"))

	// Do we crash on double removes, and will the map be empty after all keys are removed.
	m.Remove("foo.bar.com.", "ID1") // a no-op
	m.Remove("foo.bar.com.", "ID2")
	m.Remove("foo.bar.com.", "ID2") // a no-op
	keys = m.LookupValues("foo.bar.com.")
	c.Assert(len(keys), Equals, 0, Commentf("Incorrect number of values returned %v", keys))
}

func (ds *RegexpMapTestSuite) TestKeepUniqueStrings(c *C) {
	in := []string{"ID1", "ID2", "ID2"}
	out := keepUniqueStrings(in)
	for i, elem := range in[:2] {
		c.Assert(elem, Equals, out[i])
	}
}

//  reSize is the number of distinct subpatterns/regexes to benchmark with
var reSize = 100

func (ds *RegexpMapTestSuite) BenchmarkRegexSingle(c *C) {
	c.StopTimer()
	re := regexp.MustCompile("(?P<iter_>bar.foo.com.)")
	in := []byte("bar.foo.com.")
	c.StartTimer()
	for i := c.N; i > 0; i-- {
		re.FindSubmatchIndex(in)
	}
}

func (ds *RegexpMapTestSuite) BenchmarkRegexLinearSearch(c *C) {
	c.StopTimer()
	m := map[*regexp.Regexp]int{}
	for i := reSize; i > 0; i-- {
		m[regexp.MustCompile("bar.foo.com.")] = i
	}
	in := []byte("bar.foo.com.")
	c.StartTimer()
	for i := c.N; i > 0; i-- {
		for re := range m {
			re.FindSubmatchIndex(in)
		}
	}
}

func (ds *RegexpMapTestSuite) BenchmarkRegexGroups(c *C) {
	c.StopTimer()
	elements := []string{}
	var re *regexp.Regexp
	for i := reSize; i > 0; i-- {
		elements = append(elements, fmt.Sprintf("(?P<iter_%d>bar.foo.com.)", i))
	}
	re = regexp.MustCompile("^" + strings.Join(elements, "|") + "$")

	in := []byte("bar.foo.com.")
	c.StartTimer()

	for i := c.N; i > 0; i-- {
		re.FindSubmatchIndex(in)
	}
}

func RunRegexpMapBenchmark(c *C, matchAll bool) {
	c.StopTimer()
	iterations := 30000
	m := NewRegexpMap()
	matchformat := "foo%d.com."
	lastMatch := 0

	// We create the map and per each entry in the LookupValues we create one
	// regexpmap.
	// If matchAll is set, per each entry a matchall regular expression will be
	// added.
	for i := 0; i < iterations; i++ {
		key := fmt.Sprintf(matchformat, lastMatch)
		val := fmt.Sprintf("ID%d", i)
		m.Add(key, val)
		if matchAll {
			m.Add(".*", val)
		}
		if i%3 == 0 {
			lastMatch = i
		}
	}
	c.StartTimer()
	for i := 0; i < c.N; i++ {
		key := fmt.Sprintf(matchformat, lastMatch)
		val := fmt.Sprintf("ID%d", i)
		m.LookupContainsValue(key, val)
	}
	return
}

func (ds *RegexpMapTestSuite) BenchmarkLookupContainsValue(c *C) {
	RunRegexpMapBenchmark(c, false)
}

func (ds *RegexpMapTestSuite) BenchmarkLookupContainsValueWithMatchAll(c *C) {
	RunRegexpMapBenchmark(c, true)
}
<|endoftext|>


<commit_before>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package installer // import "k8s.io/helm/pkg/plugin/installer"

import (
	"errors"
	"fmt"
	"os"
	"sort"

	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"

	"k8s.io/helm/pkg/helm/helmpath"
	"k8s.io/helm/pkg/plugin/cache"
)

// VCSInstaller installs plugins from remote a repository.
type VCSInstaller struct {
	Repo    vcs.Repo
	Version string
	base
}

func existingVCSRepo(location string, home helmpath.Home) (Installer, error) {
	repo, err := vcs.NewRepo("", location)
	if err != nil {
		return nil, err
	}
	i := &VCSInstaller{
		Repo: repo,
		base: newBase(repo.Remote(), home),
	}
	return i, err
}

// NewVCSInstaller creates a new VCSInstaller.
func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) {
	key, err := cache.Key(source)
	if err != nil {
		return nil, err
	}
	cachedpath := home.Path("cache", "plugins", key)
	repo, err := vcs.NewRepo(source, cachedpath)
	if err != nil {
		return nil, err
	}
	i := &VCSInstaller{
		Repo:    repo,
		Version: version,
		base:    newBase(source, home),
	}
	return i, err
}

// Install clones a remote repository and creates a symlink to the plugin directory in HELM_HOME.
//
// Implements Installer.
func (i *VCSInstaller) Install() error {
	if err := i.sync(i.Repo); err != nil {
		return err
	}

	ref, err := i.solveVersion(i.Repo)
	if err != nil {
		return err
	}
	if ref != "" {
		if err := i.setVersion(i.Repo, ref); err != nil {
			return err
		}
	}

	if !isPlugin(i.Repo.LocalPath()) {
		return ErrMissingMetadata
	}

	return i.link(i.Repo.LocalPath())
}

// Update updates a remote repository
func (i *VCSInstaller) Update() error {
	debug("updating %s", i.Repo.Remote())
	if i.Repo.IsDirty() {
		return errors.New("plugin repo was modified")
	}
	if err := i.Repo.Update(); err != nil {
		return err
	}
	if !isPlugin(i.Repo.LocalPath()) {
		return ErrMissingMetadata
	}
	return nil
}

func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
	if i.Version == "" {
		return "", nil
	}

	if repo.IsReference(i.Version) {
		return i.Version, nil
	}

	// Create the constraint first to make sure it's valid before
	// working on the repo.
	constraint, err := semver.NewConstraint(i.Version)
	if err != nil {
		return "", err
	}

	// Get the tags
	refs, err := repo.Tags()
	if err != nil {
		return "", err
	}
	debug("found refs: %s", refs)

	// Convert and filter the list to semver.Version instances
	semvers := getSemVers(refs)

	// Sort semver list
	sort.Sort(sort.Reverse(semver.Collection(semvers)))
	for _, v := range semvers {
		if constraint.Check(v) {
			// If the constrint passes get the original reference
			ver := v.Original()
			debug("setting to %s", ver)
			return ver, nil
		}
	}

	return "", fmt.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
}

// setVersion attempts to checkout the version
func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
	debug("setting version to %q", i.Version)
	return repo.UpdateVersion(ref)
}

// sync will clone or update a remote repo.
func (i *VCSInstaller) sync(repo vcs.Repo) error {
	if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
		debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
		return repo.Get()
	}
	debug("updating %s", repo.Remote())
	return repo.Update()
}

// Filter a list of versions to only included semantic versions. The response
// is a mapping of the original version to the semantic version.
func getSemVers(refs []string) []*semver.Version {
	var sv []*semver.Version
	for _, r := range refs {
		if v, err := semver.NewVersion(r); err == nil {
			sv = append(sv, v)
		}
	}
	return sv
}
<commit_msg>fix word `constrint` to `constraint`<commit_after>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package installer // import "k8s.io/helm/pkg/plugin/installer"

import (
	"errors"
	"fmt"
	"os"
	"sort"

	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"

	"k8s.io/helm/pkg/helm/helmpath"
	"k8s.io/helm/pkg/plugin/cache"
)

// VCSInstaller installs plugins from remote a repository.
type VCSInstaller struct {
	Repo    vcs.Repo
	Version string
	base
}

func existingVCSRepo(location string, home helmpath.Home) (Installer, error) {
	repo, err := vcs.NewRepo("", location)
	if err != nil {
		return nil, err
	}
	i := &VCSInstaller{
		Repo: repo,
		base: newBase(repo.Remote(), home),
	}
	return i, err
}

// NewVCSInstaller creates a new VCSInstaller.
func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) {
	key, err := cache.Key(source)
	if err != nil {
		return nil, err
	}
	cachedpath := home.Path("cache", "plugins", key)
	repo, err := vcs.NewRepo(source, cachedpath)
	if err != nil {
		return nil, err
	}
	i := &VCSInstaller{
		Repo:    repo,
		Version: version,
		base:    newBase(source, home),
	}
	return i, err
}

// Install clones a remote repository and creates a symlink to the plugin directory in HELM_HOME.
//
// Implements Installer.
func (i *VCSInstaller) Install() error {
	if err := i.sync(i.Repo); err != nil {
		return err
	}

	ref, err := i.solveVersion(i.Repo)
	if err != nil {
		return err
	}
	if ref != "" {
		if err := i.setVersion(i.Repo, ref); err != nil {
			return err
		}
	}

	if !isPlugin(i.Repo.LocalPath()) {
		return ErrMissingMetadata
	}

	return i.link(i.Repo.LocalPath())
}

// Update updates a remote repository
func (i *VCSInstaller) Update() error {
	debug("updating %s", i.Repo.Remote())
	if i.Repo.IsDirty() {
		return errors.New("plugin repo was modified")
	}
	if err := i.Repo.Update(); err != nil {
		return err
	}
	if !isPlugin(i.Repo.LocalPath()) {
		return ErrMissingMetadata
	}
	return nil
}

func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
	if i.Version == "" {
		return "", nil
	}

	if repo.IsReference(i.Version) {
		return i.Version, nil
	}

	// Create the constraint first to make sure it's valid before
	// working on the repo.
	constraint, err := semver.NewConstraint(i.Version)
	if err != nil {
		return "", err
	}

	// Get the tags
	refs, err := repo.Tags()
	if err != nil {
		return "", err
	}
	debug("found refs: %s", refs)

	// Convert and filter the list to semver.Version instances
	semvers := getSemVers(refs)

	// Sort semver list
	sort.Sort(sort.Reverse(semver.Collection(semvers)))
	for _, v := range semvers {
		if constraint.Check(v) {
			// If the constraint passes get the original reference
			ver := v.Original()
			debug("setting to %s", ver)
			return ver, nil
		}
	}

	return "", fmt.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
}

// setVersion attempts to checkout the version
func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
	debug("setting version to %q", i.Version)
	return repo.UpdateVersion(ref)
}

// sync will clone or update a remote repo.
func (i *VCSInstaller) sync(repo vcs.Repo) error {
	if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
		debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
		return repo.Get()
	}
	debug("updating %s", repo.Remote())
	return repo.Update()
}

// Filter a list of versions to only included semantic versions. The response
// is a mapping of the original version to the semantic version.
func getSemVers(refs []string) []*semver.Version {
	var sv []*semver.Version
	for _, r := range refs {
		if v, err := semver.NewVersion(r); err == nil {
			sv = append(sv, v)
		}
	}
	return sv
}
<|endoftext|>


<commit_before>package rkenodeconfigserver

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/pkg/errors"
	"github.com/rancher/norman/types/slice"
	"github.com/rancher/rancher/pkg/image"
	"github.com/rancher/rancher/pkg/librke"
	"github.com/rancher/rancher/pkg/rkecerts"
	"github.com/rancher/rancher/pkg/rkeworker"
	"github.com/rancher/rancher/pkg/settings"
	"github.com/rancher/rancher/pkg/tunnelserver"
	cluster2 "github.com/rancher/rke/cluster"
	"github.com/rancher/rke/services"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
)

var (
	b2Mount = "/mnt/sda1"
)

type RKENodeConfigServer struct {
	auth   *tunnelserver.Authorizer
	lookup *rkecerts.BundleLookup
}

func Handler(auth *tunnelserver.Authorizer, scaledContext *config.ScaledContext) http.Handler {
	return &RKENodeConfigServer{
		auth:   auth,
		lookup: rkecerts.NewLookup(scaledContext.Core.Namespaces(""), scaledContext.K8sClient.CoreV1()),
	}
}

func (n *RKENodeConfigServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	// 404 tells the client to continue without plan
	// 5xx tells the client to try again later for plan

	client, ok, err := n.auth.Authorize(req)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}

	if !ok {
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}

	if client.Node == nil {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	if client.Cluster.Status.Driver == "" {
		rw.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	if client.Cluster.Status.Driver != v3.ClusterDriverRKE {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	if client.Node.Status.NodeConfig == nil {
		rw.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	var nodeConfig *rkeworker.NodeConfig
	if isNonWorkerOnly(client.Node.Status.NodeConfig.Role) {
		nodeConfig, err = n.nonWorkerConfig(req.Context(), client.Cluster, client.Node)
	} else {
		if client.Cluster.Status.AppliedSpec.RancherKubernetesEngineConfig == nil {
			rw.WriteHeader(http.StatusServiceUnavailable)
			return
		}
		nodeConfig, err = n.nodeConfig(req.Context(), client.Cluster, client.Node)
	}

	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(rw).Encode(nodeConfig); err != nil {
		logrus.Errorf("failed to write nodeConfig to agent: %v", err)
	}
}

func isNonWorkerOnly(role []string) bool {
	if slice.ContainsString(role, services.ETCDRole) ||
		slice.ContainsString(role, services.ControlRole) {
		return true
	}
	return false
}

func (n *RKENodeConfigServer) nonWorkerConfig(ctx context.Context, cluster *v3.Cluster, node *v3.Node) (*rkeworker.NodeConfig, error) {
	rkeConfig := cluster.Status.AppliedSpec.RancherKubernetesEngineConfig
	if rkeConfig == nil {
		rkeConfig = &v3.RancherKubernetesEngineConfig{}
	}

	rkeConfig = rkeConfig.DeepCopy()
	rkeConfig.Nodes = []v3.RKEConfigNode{
		*node.Status.NodeConfig,
	}
	rkeConfig.Nodes[0].Role = []string{services.WorkerRole, services.ETCDRole, services.ControlRole}

	infos, err := librke.GetDockerInfo(node)
	if err != nil {
		return nil, err
	}

	plan, err := librke.New().GeneratePlan(ctx, rkeConfig, infos)
	if err != nil {
		return nil, err
	}

	nc := &rkeworker.NodeConfig{
		ClusterName: cluster.Name,
	}

	for _, tempNode := range plan.Nodes {
		if tempNode.Address == node.Status.NodeConfig.Address {
			b2d := strings.Contains(infos[tempNode.Address].OperatingSystem, cluster2.B2DOS)
			nc.Processes = augmentProcesses(tempNode.Processes, false, b2d)
			return nc, nil
		}
	}

	return nil, fmt.Errorf("failed to find plan for non-worker %s", node.Status.NodeConfig.Address)
}

func (n *RKENodeConfigServer) nodeConfig(ctx context.Context, cluster *v3.Cluster, node *v3.Node) (*rkeworker.NodeConfig, error) {
	spec := cluster.Status.AppliedSpec

	bundle, err := n.lookup.Lookup(cluster)
	if err != nil {
		return nil, err
	}

	bundle = bundle.ForNode(spec.RancherKubernetesEngineConfig, node.Status.NodeConfig.Address)

	certString, err := bundle.Marshal()
	if err != nil {
		return nil, errors.Wrapf(err, "failed to marshall bundle")
	}

	infos, err := librke.GetDockerInfo(node)
	if err != nil {
		return nil, err
	}

	plan, err := librke.New().GeneratePlan(ctx, spec.RancherKubernetesEngineConfig, infos)
	if err != nil {
		return nil, err
	}

	nc := &rkeworker.NodeConfig{
		Certs:       certString,
		ClusterName: cluster.Name,
	}

	for _, tempNode := range plan.Nodes {
		if tempNode.Address == node.Status.NodeConfig.Address {
			b2d := strings.Contains(infos[tempNode.Address].OperatingSystem, cluster2.B2DOS)
			nc.Processes = augmentProcesses(tempNode.Processes, true, b2d)
			nc.Files = tempNode.Files
			return nc, nil
		}
	}

	return nil, fmt.Errorf("failed to find plan for %s", node.Status.NodeConfig.Address)
}

func augmentProcesses(processes map[string]v3.Process, worker, b2d bool) map[string]v3.Process {
	var shared []string

	if b2d {
		shared = append(shared, b2Mount)
	}

	for _, process := range processes {
		for _, bind := range process.Binds {
			parts := strings.Split(bind, ":")
			if len(parts) > 2 && strings.Contains(parts[2], "shared") {
				shared = append(shared, parts[0])
			}
		}
	}

	if len(shared) > 0 {
		args := []string{"--", "share-root.sh"}
		args = append(args, shared...)

		processes["share-mnt"] = v3.Process{
			Name:          "share-mnt",
			Args:          args,
			Image:         image.Resolve(settings.AgentImage.Get()),
			Binds:         []string{"/var/run:/var/run"},
			NetworkMode:   "host",
			RestartPolicy: "always",
			PidMode:       "host",
			Privileged:    true,
		}
	}

	if worker {
		// not sure if we really need this anymore
		delete(processes, "etcd")
	} else {
		if p, ok := processes["share-mnt"]; ok {
			processes = map[string]v3.Process{
				"share-mnt": p,
			}
		} else {
			processes = nil
		}
	}

	for _, p := range processes {
		for i, bind := range p.Binds {
			parts := strings.Split(bind, ":")
			if len(parts) > 1 && parts[1] == "/etc/kubernetes" {
				parts[0] = parts[1]
				p.Binds[i] = strings.Join(parts, ":")
			}
		}
	}

	return processes
}
<commit_msg>Fix B2DOS constant<commit_after>package rkenodeconfigserver

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/pkg/errors"
	"github.com/rancher/norman/types/slice"
	"github.com/rancher/rancher/pkg/image"
	"github.com/rancher/rancher/pkg/librke"
	"github.com/rancher/rancher/pkg/rkecerts"
	"github.com/rancher/rancher/pkg/rkeworker"
	"github.com/rancher/rancher/pkg/settings"
	"github.com/rancher/rancher/pkg/tunnelserver"
	"github.com/rancher/rke/hosts"
	"github.com/rancher/rke/services"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
)

var (
	b2Mount = "/mnt/sda1"
)

type RKENodeConfigServer struct {
	auth   *tunnelserver.Authorizer
	lookup *rkecerts.BundleLookup
}

func Handler(auth *tunnelserver.Authorizer, scaledContext *config.ScaledContext) http.Handler {
	return &RKENodeConfigServer{
		auth:   auth,
		lookup: rkecerts.NewLookup(scaledContext.Core.Namespaces(""), scaledContext.K8sClient.CoreV1()),
	}
}

func (n *RKENodeConfigServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	// 404 tells the client to continue without plan
	// 5xx tells the client to try again later for plan

	client, ok, err := n.auth.Authorize(req)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}

	if !ok {
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}

	if client.Node == nil {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	if client.Cluster.Status.Driver == "" {
		rw.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	if client.Cluster.Status.Driver != v3.ClusterDriverRKE {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	if client.Node.Status.NodeConfig == nil {
		rw.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	var nodeConfig *rkeworker.NodeConfig
	if isNonWorkerOnly(client.Node.Status.NodeConfig.Role) {
		nodeConfig, err = n.nonWorkerConfig(req.Context(), client.Cluster, client.Node)
	} else {
		if client.Cluster.Status.AppliedSpec.RancherKubernetesEngineConfig == nil {
			rw.WriteHeader(http.StatusServiceUnavailable)
			return
		}
		nodeConfig, err = n.nodeConfig(req.Context(), client.Cluster, client.Node)
	}

	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(rw).Encode(nodeConfig); err != nil {
		logrus.Errorf("failed to write nodeConfig to agent: %v", err)
	}
}

func isNonWorkerOnly(role []string) bool {
	if slice.ContainsString(role, services.ETCDRole) ||
		slice.ContainsString(role, services.ControlRole) {
		return true
	}
	return false
}

func (n *RKENodeConfigServer) nonWorkerConfig(ctx context.Context, cluster *v3.Cluster, node *v3.Node) (*rkeworker.NodeConfig, error) {
	rkeConfig := cluster.Status.AppliedSpec.RancherKubernetesEngineConfig
	if rkeConfig == nil {
		rkeConfig = &v3.RancherKubernetesEngineConfig{}
	}

	rkeConfig = rkeConfig.DeepCopy()
	rkeConfig.Nodes = []v3.RKEConfigNode{
		*node.Status.NodeConfig,
	}
	rkeConfig.Nodes[0].Role = []string{services.WorkerRole, services.ETCDRole, services.ControlRole}

	infos, err := librke.GetDockerInfo(node)
	if err != nil {
		return nil, err
	}

	plan, err := librke.New().GeneratePlan(ctx, rkeConfig, infos)
	if err != nil {
		return nil, err
	}

	nc := &rkeworker.NodeConfig{
		ClusterName: cluster.Name,
	}

	for _, tempNode := range plan.Nodes {
		if tempNode.Address == node.Status.NodeConfig.Address {
			b2d := strings.Contains(infos[tempNode.Address].OperatingSystem, hosts.B2DOS)
			nc.Processes = augmentProcesses(tempNode.Processes, false, b2d)
			return nc, nil
		}
	}

	return nil, fmt.Errorf("failed to find plan for non-worker %s", node.Status.NodeConfig.Address)
}

func (n *RKENodeConfigServer) nodeConfig(ctx context.Context, cluster *v3.Cluster, node *v3.Node) (*rkeworker.NodeConfig, error) {
	spec := cluster.Status.AppliedSpec

	bundle, err := n.lookup.Lookup(cluster)
	if err != nil {
		return nil, err
	}

	bundle = bundle.ForNode(spec.RancherKubernetesEngineConfig, node.Status.NodeConfig.Address)

	certString, err := bundle.Marshal()
	if err != nil {
		return nil, errors.Wrapf(err, "failed to marshall bundle")
	}

	infos, err := librke.GetDockerInfo(node)
	if err != nil {
		return nil, err
	}

	plan, err := librke.New().GeneratePlan(ctx, spec.RancherKubernetesEngineConfig, infos)
	if err != nil {
		return nil, err
	}

	nc := &rkeworker.NodeConfig{
		Certs:       certString,
		ClusterName: cluster.Name,
	}

	for _, tempNode := range plan.Nodes {
		if tempNode.Address == node.Status.NodeConfig.Address {
			b2d := strings.Contains(infos[tempNode.Address].OperatingSystem, hosts.B2DOS)
			nc.Processes = augmentProcesses(tempNode.Processes, true, b2d)
			nc.Files = tempNode.Files
			return nc, nil
		}
	}

	return nil, fmt.Errorf("failed to find plan for %s", node.Status.NodeConfig.Address)
}

func augmentProcesses(processes map[string]v3.Process, worker, b2d bool) map[string]v3.Process {
	var shared []string

	if b2d {
		shared = append(shared, b2Mount)
	}

	for _, process := range processes {
		for _, bind := range process.Binds {
			parts := strings.Split(bind, ":")
			if len(parts) > 2 && strings.Contains(parts[2], "shared") {
				shared = append(shared, parts[0])
			}
		}
	}

	if len(shared) > 0 {
		args := []string{"--", "share-root.sh"}
		args = append(args, shared...)

		processes["share-mnt"] = v3.Process{
			Name:          "share-mnt",
			Args:          args,
			Image:         image.Resolve(settings.AgentImage.Get()),
			Binds:         []string{"/var/run:/var/run"},
			NetworkMode:   "host",
			RestartPolicy: "always",
			PidMode:       "host",
			Privileged:    true,
		}
	}

	if worker {
		// not sure if we really need this anymore
		delete(processes, "etcd")
	} else {
		if p, ok := processes["share-mnt"]; ok {
			processes = map[string]v3.Process{
				"share-mnt": p,
			}
		} else {
			processes = nil
		}
	}

	for _, p := range processes {
		for i, bind := range p.Binds {
			parts := strings.Split(bind, ":")
			if len(parts) > 1 && parts[1] == "/etc/kubernetes" {
				parts[0] = parts[1]
				p.Binds[i] = strings.Join(parts, ":")
			}
		}
	}

	return processes
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package rest

import (
	"flag"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/emicklei/go-restful"
	"github.com/gorilla/websocket"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/ghttp"
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/httpstream"
	"k8s.io/apimachinery/pkg/util/httpstream/spdy"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/virt-controller/services"
)

var _ = Describe("VirtualMachineInstance Subresources", func() {
	var server *ghttp.Server
	var backend *ghttp.Server
	var request *restful.Request
	var response *restful.Response
	var wsURL string

	log.Log.SetIOWriter(GinkgoWriter)

	configCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil)
	pvcCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil)
	app := SubresourceAPIApp{}
	BeforeEach(func() {
		server = ghttp.NewServer()
		backend = ghttp.NewServer()
		flag.Set("kubeconfig", "")
		flag.Set("master", server.URL())
		app.VirtCli, _ = kubecli.GetKubevirtClientFromFlags(server.URL(), "")

		request = restful.NewRequest(&http.Request{})
		response = restful.NewResponse(httptest.NewRecorder())
		wsURL = "ws" + strings.TrimPrefix(backend.URL(), "http")

		// To emulate rest server
		backend.AppendHandlers(
			ghttp.CombineHandlers(
				ghttp.VerifyRequest("GET", "/"),
				func(w http.ResponseWriter, r *http.Request) {
					request.Request = r
					response.ResponseWriter = w
					app.VNCRequestHandler(request, response)
				},
			),
		)
	})

	Context("Subresource api", func() {
		It("should find matching pod for running VirtualMachineInstance", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache)

			pod, err := templateService.RenderLaunchManifest(vmi)
			Expect(err).ToNot(HaveOccurred())
			pod.ObjectMeta.Name = "madeup-name"

			pod.Spec.NodeName = "mynode"
			pod.Status.Phase = k8sv1.PodRunning

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}
			podList.Items = append(podList.Items, *pod)

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
			)

			podName, httpStatusCode, err := app.remoteExecInfo(vmi)
			Expect(err).ToNot(HaveOccurred())

			Expect(podName).To(Equal("madeup-name"))
			Expect(httpStatusCode).To(Equal(http.StatusOK))
			close(done)
		}, 5)

		It("should fail if VirtualMachineInstance is not in running state", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Succeeded
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			_, httpStatusCode, err := app.remoteExecInfo(vmi)

			Expect(err).To(HaveOccurred())
			Expect(httpStatusCode).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail no matching pod is found", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
			)

			_, httpStatusCode, err := app.remoteExecInfo(vmi)

			Expect(err).To(HaveOccurred())
			Expect(httpStatusCode).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail with no 'name' path param", func(done Done) {

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail with no 'namespace' path param", func(done Done) {

			request.PathParameters()["name"] = "testvmi"

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail if vmi is not found", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusNotFound, nil),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusNotFound))
			close(done)
		}, 5)

		It("should fail with internal at fetching vmi errors", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusServiceUnavailable, nil),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail with no graphics device at VNC connections", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			flag := false
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			vmi.Spec.Domain.Devices.AutoattachGraphicsDevice = &flag

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
			)
			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail with no graphics device at VNC connections", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			flag := false
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			vmi.Spec.Domain.Devices.AutoattachGraphicsDevice = &flag

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("Should pass client websocket io to server SPDY io", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache)

			pod, err := templateService.RenderLaunchManifest(vmi)
			Expect(err).ToNot(HaveOccurred())
			pod.ObjectMeta.Name = "madeup-name"

			pod.Spec.NodeName = "mynode"
			pod.Status.Phase = k8sv1.PodRunning

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}
			podList.Items = append(podList.Items, *pod)

			newStreamChannel := make(chan httpstream.Stream)

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("POST", "/api/v1/namespaces/default/pods/madeup-name/exec"),
					func(w http.ResponseWriter, r *http.Request) {
						// TODO: Just testing code we should use real SPDY stuff
						upgrader := spdy.NewResponseUpgrader()
						upgrader.UpgradeResponse(w, r,
							func(stream httpstream.Stream, replySent <-chan struct{}) error {
								newStreamChannel <- stream
								return nil
							})
					},
				),
			)

			ws, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusSwitchingProtocols))

			streamType := func(stream httpstream.Stream) string {
				return stream.Headers().Get("streamType")
			}

			// Receive accepted stream
			// FIXME: It's no good to depend on order, implementation
			// can change
			streamError := <-newStreamChannel
			Expect(streamType(streamError)).To(Equal("error"))
			streamStdin := <-newStreamChannel
			Expect(streamType(streamStdin)).To(Equal("stdin"))
			streamStdout := <-newStreamChannel
			Expect(streamType(streamStdout)).To(Equal("stdout"))
			streamStderror := <-newStreamChannel
			Expect(streamType(streamStderror)).To(Equal("stderr"))

			expected := []byte("Hello")
			err = ws.WriteMessage(websocket.BinaryMessage, expected)
			Expect(err).NotTo(HaveOccurred())

			obtained := make([]byte, len(expected))
			_, err = io.ReadFull(streamStdin, obtained)
			Expect(err).NotTo(HaveOccurred())
			Expect(obtained).To(Equal(expected))

			expected = []byte("World")
			_, err = streamStdout.Write(expected)
			Expect(err).NotTo(HaveOccurred())

			_, obtained, err = ws.ReadMessage()
			Expect(err).NotTo(HaveOccurred())
			Expect(obtained).To(Equal(expected))

			// TODO: Check error streams
			defer ws.Close()
			close(done)

		}, 5)

	})

	AfterEach(func() {
		server.Close()
		backend.Close()
	})
})
<commit_msg>Remove old TODOs from subresource tests<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package rest

import (
	"flag"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/emicklei/go-restful"
	"github.com/gorilla/websocket"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/ghttp"
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/httpstream"
	"k8s.io/apimachinery/pkg/util/httpstream/spdy"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/virt-controller/services"
)

var _ = Describe("VirtualMachineInstance Subresources", func() {
	var server *ghttp.Server
	var backend *ghttp.Server
	var request *restful.Request
	var response *restful.Response
	var wsURL string

	log.Log.SetIOWriter(GinkgoWriter)

	configCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil)
	pvcCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil)
	app := SubresourceAPIApp{}
	BeforeEach(func() {
		server = ghttp.NewServer()
		backend = ghttp.NewServer()
		flag.Set("kubeconfig", "")
		flag.Set("master", server.URL())
		app.VirtCli, _ = kubecli.GetKubevirtClientFromFlags(server.URL(), "")

		request = restful.NewRequest(&http.Request{})
		response = restful.NewResponse(httptest.NewRecorder())
		wsURL = "ws" + strings.TrimPrefix(backend.URL(), "http")

		// To emulate rest server
		backend.AppendHandlers(
			ghttp.CombineHandlers(
				ghttp.VerifyRequest("GET", "/"),
				func(w http.ResponseWriter, r *http.Request) {
					request.Request = r
					response.ResponseWriter = w
					app.VNCRequestHandler(request, response)
				},
			),
		)
	})

	Context("Subresource api", func() {
		It("should find matching pod for running VirtualMachineInstance", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache)

			pod, err := templateService.RenderLaunchManifest(vmi)
			Expect(err).ToNot(HaveOccurred())
			pod.ObjectMeta.Name = "madeup-name"

			pod.Spec.NodeName = "mynode"
			pod.Status.Phase = k8sv1.PodRunning

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}
			podList.Items = append(podList.Items, *pod)

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
			)

			podName, httpStatusCode, err := app.remoteExecInfo(vmi)
			Expect(err).ToNot(HaveOccurred())

			Expect(podName).To(Equal("madeup-name"))
			Expect(httpStatusCode).To(Equal(http.StatusOK))
			close(done)
		}, 5)

		It("should fail if VirtualMachineInstance is not in running state", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Succeeded
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			_, httpStatusCode, err := app.remoteExecInfo(vmi)

			Expect(err).To(HaveOccurred())
			Expect(httpStatusCode).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail no matching pod is found", func(done Done) {
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
			)

			_, httpStatusCode, err := app.remoteExecInfo(vmi)

			Expect(err).To(HaveOccurred())
			Expect(httpStatusCode).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail with no 'name' path param", func(done Done) {

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail with no 'namespace' path param", func(done Done) {

			request.PathParameters()["name"] = "testvmi"

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail if vmi is not found", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusNotFound, nil),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusNotFound))
			close(done)
		}, 5)

		It("should fail with internal at fetching vmi errors", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusServiceUnavailable, nil),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
			close(done)
		}, 5)

		It("should fail with no graphics device at VNC connections", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			flag := false
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			vmi.Spec.Domain.Devices.AutoattachGraphicsDevice = &flag

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
			)
			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("should fail with no graphics device at VNC connections", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			flag := false
			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())
			vmi.Spec.Domain.Devices.AutoattachGraphicsDevice = &flag

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
			)

			app.VNCRequestHandler(request, response)
			Expect(response.Error()).To(HaveOccurred())
			Expect(response.StatusCode()).To(Equal(http.StatusBadRequest))
			close(done)
		}, 5)

		It("Should pass client websocket io to server SPDY io", func(done Done) {

			request.PathParameters()["name"] = "testvmi"
			request.PathParameters()["namespace"] = "default"

			vmi := v1.NewMinimalVMI("testvmi")
			vmi.Status.Phase = v1.Running
			vmi.ObjectMeta.SetUID(uuid.NewUUID())

			templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache)

			pod, err := templateService.RenderLaunchManifest(vmi)
			Expect(err).ToNot(HaveOccurred())
			pod.ObjectMeta.Name = "madeup-name"

			pod.Spec.NodeName = "mynode"
			pod.Status.Phase = k8sv1.PodRunning

			podList := k8sv1.PodList{}
			podList.Items = []k8sv1.Pod{}
			podList.Items = append(podList.Items, *pod)

			newStreamChannel := make(chan httpstream.Stream)

			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/apis/kubevirt.io/v1alpha2/namespaces/default/virtualmachineinstances/testvmi"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"),
					ghttp.RespondWithJSONEncoded(http.StatusOK, podList),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("POST", "/api/v1/namespaces/default/pods/madeup-name/exec"),
					func(w http.ResponseWriter, r *http.Request) {
						upgrader := spdy.NewResponseUpgrader()
						upgrader.UpgradeResponse(w, r,
							func(stream httpstream.Stream, replySent <-chan struct{}) error {
								newStreamChannel <- stream
								return nil
							})
					},
				),
			)

			ws, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusSwitchingProtocols))

			streamType := func(stream httpstream.Stream) string {
				return stream.Headers().Get("streamType")
			}

			// Receive accepted stream
			// FIXME: It's no good to depend on order, implementation
			// can change
			streamError := <-newStreamChannel
			Expect(streamType(streamError)).To(Equal("error"))
			streamStdin := <-newStreamChannel
			Expect(streamType(streamStdin)).To(Equal("stdin"))
			streamStdout := <-newStreamChannel
			Expect(streamType(streamStdout)).To(Equal("stdout"))
			streamStderror := <-newStreamChannel
			Expect(streamType(streamStderror)).To(Equal("stderr"))

			expected := []byte("Hello")
			err = ws.WriteMessage(websocket.BinaryMessage, expected)
			Expect(err).NotTo(HaveOccurred())

			obtained := make([]byte, len(expected))
			_, err = io.ReadFull(streamStdin, obtained)
			Expect(err).NotTo(HaveOccurred())
			Expect(obtained).To(Equal(expected))

			expected = []byte("World")
			_, err = streamStdout.Write(expected)
			Expect(err).NotTo(HaveOccurred())

			_, obtained, err = ws.ReadMessage()
			Expect(err).NotTo(HaveOccurred())
			Expect(obtained).To(Equal(expected))

			// TODO: Check error streams
			defer ws.Close()
			close(done)

		}, 5)

	})

	AfterEach(func() {
		server.Close()
		backend.Close()
	})
})
<|endoftext|>


<commit_before>// Copyright 2014-2015 The DevMine 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 repo provides functions for accessing VCS information.
package repo

import (
	"archive/tar"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/DevMine/repotool/config"
	"github.com/DevMine/repotool/model"
)

// Repository types.
const (
	Git = "git"
	Hg  = "mercurial"
	SVN = "subversion"
	Bzr = "bazaar"
	CVS = "cvs"
)

// suppVCS is a list of supported VCS.
var suppVCS = []string{
	Git,
}

// Repo interface defines what needs to be implemented to construct a Repo object.
type Repo interface {
	// FetchCommits populates Commits attribute with all commits of a repository.
	FetchCommits() error

	// GetRepository returns a repository structure from a repo.
	GetRepository() *model.Repository

	// GetName returns the name of a repo.
	GetName() string

	// GetVCS returns the VCS typoe of a repo.
	GetVCS() string

	// GetCloneURL returns the clone URL of a repo.
	GetCloneURL() string

	// GetClonePath returns the clone path of a repo.
	GetClonePath() string

	// GetDefaultBranch returns the default branch of a repo.
	GetDefaultBranch() string

	// GetCommits returns the list of commits of a repo.
	GetCommits() []model.Commit

	// Cleanup needs to be called when done using the repository. It performs
	// some housekeeping if necessary.
	Cleanup() error
}

var _ Repo = (*gitRepo)(nil)

// New creates a new Repo object.
func New(cfg config.Config, path string) (Repo, error) {
	var repo Repo

	// check for git repo
	if vcs, err := detectVCS(path); err == nil {

		tmpPath := path
		if strings.HasSuffix(path, ".tar") {
			tmpPath, err = untarGitFolder(cfg.TmpDir, path)
			if err != nil {
				return nil, err
			}
			path = strings.TrimSuffix(path, ".tar")
		}
		cloneURL, err := extractGitURL(tmpPath)
		if err != nil {
			return nil, err
		}

		branch, err := extractGitDefaultBranch(tmpPath)
		if err != nil {
			return nil, err
		}

		repository := model.Repository{
			Name:          extractName(path),
			VCS:           vcs,
			CloneURL:      *cloneURL,
			ClonePath:     path,
			DefaultBranch: *branch,
		}
		repo, err = newGitRepo(cfg, repository, tmpPath)
		if err != nil {
			return nil, err
		}

		return repo, nil
	}

	return nil, errors.New("unsupported repository type")
}

// detect vcs used
func detectVCS(path string) (string, error) {
	if strings.HasSuffix(path, ".tar") {
		archiveFile, err := os.Open(path)
		if err != nil {
			return "", err
		}
		defer archiveFile.Close()

		tr := tar.NewReader(archiveFile)

		for {
			hdr, err := tr.Next()
			if err == io.EOF {
				break
			}
			if err != nil {
				return "", err
			}
			if strings.Contains(hdr.Name, ".git") {
				return Git, nil
			}
		}
	} else if _, err := os.Stat(filepath.Join(path, ".git")); err == nil {
		return Git, nil
	}

	return "", errors.New("VCS type not found")
}

// extractName extracts to name of a repository given its clone URL.
func extractName(path string) string {
	return filepath.Base(path)
}
<commit_msg>repo: fix broken logic for VCS type detection<commit_after>// Copyright 2014-2015 The DevMine 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 repo provides functions for accessing VCS information.
package repo

import (
	"archive/tar"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/DevMine/repotool/config"
	"github.com/DevMine/repotool/model"
)

// Repository types.
const (
	Git = "git"
	Hg  = "mercurial"
	SVN = "subversion"
	Bzr = "bazaar"
	CVS = "cvs"
)

// suppVCS is a list of supported VCS.
var suppVCS = []string{
	Git,
}

// Repo interface defines what needs to be implemented to construct a Repo object.
type Repo interface {
	// FetchCommits populates Commits attribute with all commits of a repository.
	FetchCommits() error

	// GetRepository returns a repository structure from a repo.
	GetRepository() *model.Repository

	// GetName returns the name of a repo.
	GetName() string

	// GetVCS returns the VCS typoe of a repo.
	GetVCS() string

	// GetCloneURL returns the clone URL of a repo.
	GetCloneURL() string

	// GetClonePath returns the clone path of a repo.
	GetClonePath() string

	// GetDefaultBranch returns the default branch of a repo.
	GetDefaultBranch() string

	// GetCommits returns the list of commits of a repo.
	GetCommits() []model.Commit

	// Cleanup needs to be called when done using the repository. It performs
	// some housekeeping if necessary.
	Cleanup() error
}

var _ Repo = (*gitRepo)(nil)

// New creates a new Repo object.
func New(cfg config.Config, path string) (Repo, error) {
	var repo Repo

	vcs, err := detectVCS(path)
	if err != nil {
		return nil, err
	}

	switch vcs {
	case Git:
		tmpPath := path
		if strings.HasSuffix(path, ".tar") {
			tmpPath, err = untarGitFolder(cfg.TmpDir, path)
			if err != nil {
				return nil, err
			}
			path = strings.TrimSuffix(path, ".tar")
		}
		cloneURL, err := extractGitURL(tmpPath)
		if err != nil {
			return nil, err
		}

		branch, err := extractGitDefaultBranch(tmpPath)
		if err != nil {
			return nil, err
		}

		repository := model.Repository{
			Name:          extractName(path),
			VCS:           vcs,
			CloneURL:      *cloneURL,
			ClonePath:     path,
			DefaultBranch: *branch,
		}
		repo, err = newGitRepo(cfg, repository, tmpPath)
		if err != nil {
			return nil, err
		}

		return repo, nil
	}

	return nil, errors.New("unsupported repository type")
}

// detectVCS attempts at detecting the VCS of the repository. It can take
// either a directory or a tar archive version of a repository as argument.
func detectVCS(path string) (string, error) {
	// check tar archive case
	if strings.HasSuffix(path, ".tar") {
		archiveFile, err := os.Open(path)
		if err != nil {
			return "", err
		}
		defer archiveFile.Close()

		// only the relative path shall be stored in the archive
		path = filepath.Base(strings.TrimSuffix(path, ".tar"))

		tr := tar.NewReader(archiveFile)

		for {
			hdr, err := tr.Next()
			if err == io.EOF {
				break
			}
			if err != nil {
				return "", err
			}

			mode := hdr.FileInfo().Mode()
			if mode&os.ModeDir != 0 {
				// remove trailing /, if any
				dir := strings.TrimSuffix(hdr.Name, "/")
				switch dir {
				// is it a git repository?
				// (only the archive root's .git directory is valid for the check)
				case filepath.Join(path, ".git"):
					return Git, nil
				}
			}
		}
	} else {
		// is it a git repository?
		if _, err := os.Stat(filepath.Join(path, ".git")); err == nil {
			return Git, nil
		}
	}

	return "", errors.New("VCS type not found")
}

// extractName extracts to name of a repository given its clone URL.
func extractName(path string) string {
	return filepath.Base(path)
}
<|endoftext|>


<commit_before>// Unfancy resources embedding with Go.

package resources

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"text/template"
)

type File interface {
	io.Reader
	Stat() (os.FileInfo, error)
}

//Create a new Package.
func New() *Package {
	return &Package{
		Config: Config{
			Pkg:     "resources",
			Var:     "FS",
			Declare: true,
		},
		Files: make(map[string]File),
	}
}

//Configuration defines some details about the output Go file.
// Pkg      the package name to use.
// Var      the variable name to assign the file system to.
// Tag      the build tag for the generated file, leave empty for not tag.
// Declare  dictates whatever there should be a defintion for the variable
//          in the output file or not, it will use the type http.FileSystem.
type Config struct {
	Pkg     string
	Var     string
	Tag     string
	Declare bool
}

type Package struct {
	Config
	Files map[string]File
}

//Add a file to the package at the give path.
func (p *Package) Add(path string, file File) {
	p.Files[path] = file
}

func (p *Package) AddFile(file string, path string) error {
	f, err := os.Open(file)
	if err != nil {
		return err
	}

	p.Files[path] = f

	return nil
}

//Build the package
func (p *Package) Build() (*bytes.Buffer, error) {
	out := new(bytes.Buffer)
	return out, pkg.Execute(out, p)
}

//Write the build to a file.
func (p *Package) Write(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	buff, err := p.Build()

	if err != nil {
		return err
	}

	_, err = buff.WriteTo(f)
	return err
}

//Template

var pkg *template.Template

func reader(input io.Reader) (string, error) {

	var (
		buff       bytes.Buffer
		err        error
		blockwidth int = 12
		curblock   int = 0
	)

	b := make([]byte, blockwidth)

	for n, err := input.Read(b); err == nil; n, err = input.Read(b) {
		for i := 0; i < n; i++ {
			fmt.Fprintf(&buff, "0x%02x,", b[i])
			curblock++
			if curblock < blockwidth {
				continue
			}
			buff.Write([]byte{'\n'})
			buff.Write([]byte{'\t', '\t'})
			curblock = 0
		}
	}

	return buff.String(), err
}

func init() {

	pkg = template.Must(template.New("file").Funcs(template.FuncMap{"reader": reader}).Parse(` File{
	  data: []byte{
	{{ reader . }}
  },
  fi: FileInfo {
	name:    "{{ .Stat.Name }}",
    size:    {{ .Stat.Size }},
	modTime: time.Unix({{ .Stat.ModTime.Unix }},{{ .Stat.ModTime.UnixNano }}),
    isDir:   {{ .Stat.IsDir }},
  },
}`))

	pkg = template.Must(pkg.New("pkg").Parse(`{{ if .Tag }}// +build {{ .Tag }}

{{ end }}//Generated by github.com/omeid/slurp/resources
package {{ .Pkg }}

import (
  "net/http"
  "time"
  "bytes"
  "os"
  "path/filepath"
  "strings"
  "errors"
)

{{ if .Declare }}
var {{ .Var }} http.FileSystem
{{ end }}

// Helper functions for easier file access.
func Open(name string) (http.File, error) {
	return {{ .Var }}.Open(name)
}

// http.FileSystem implementation.
type FileSystem struct {
	files map[string]File
}

func (fs *FileSystem) Open(name string) (http.File, error) {
	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
	    strings.Contains(name, "\x00") {
		return nil, errors.New("http: invalid character in file path")
	}
	file, ok := fs.files[name]
	if !ok {
		files := []os.FileInfo{}
		for path, file := range fs.files {
			if strings.HasPrefix(path, name) {
				s, _ := file.Stat()
				files = append(files, s)
			}
		}

		if len(files) < 0 {
			return nil, os.ErrNotExist
		}

		//We have a directory.
		return &File{
		  fi: FileInfo{
				isDir: true,
				files: files,
			}}, nil
	}
	file.Reader = bytes.NewReader(file.data)
	return &file, nil
}

type File struct {
	*bytes.Reader
	data []byte
	fi FileInfo
}

// A noop-closer.
func (f *File) Close() error {
	return nil
}

func (f *File) Readdir(count int) ([]os.FileInfo, error) {
  return nil, os.ErrNotExist
}

func (f *File) Stat() (os.FileInfo, error) {
  return &f.fi, nil
}

type FileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	isDir   bool
	sys     interface{}

	files []os.FileInfo
}

func (f *FileInfo) Name() string {
	return f.name
}
func (f *FileInfo) Size() int64 {
	return f.size
}

func (f *FileInfo) Mode() os.FileMode {
	return f.mode
}

func (f *FileInfo) ModTime() time.Time {
	return f.modTime
}

func (f *FileInfo) IsDir() bool {
	return f.isDir
}

func (f *FileInfo) Readdir(count int) ([]os.FileInfo, error) {
	return f.files, nil
}

func (f *FileInfo) Sys() interface{} {
	return f.sys
}

func init() {
  {{ .Var }} = &FileSystem{
		files: map[string]File{
		  {{range $path, $file := .Files }} "/{{ $path }}": {{ template "file" $file }}, {{ end }}
		},
	  }
}
`))
}
<commit_msg>Fix doc.<commit_after>// Unfancy resources embedding with Go.

package resources

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"text/template"
)

type File interface {
	io.Reader
	Stat() (os.FileInfo, error)
}

//Create a new Package.
func New() *Package {
	return &Package{
		Config: Config{
			Pkg:     "resources",
			Var:     "FS",
			Declare: true,
		},
		Files: make(map[string]File),
	}
}

//Configuration defines some details about the output Go file.
type Config struct {
	Pkg     string  // Pkg      the package name to use.
	Var     string  // Var      the variable name to assign the file system to.
	Tag     string  // Tag      the build tag for the generated file, leave empty for not tag.
	Declare bool    // Declare  dictates whatever there should be a defintion for the variable in the output file or not, it will use the type http.FileSystem.
  }

type Package struct {
	Config
	Files map[string]File
}

//Add a file to the package at the give path.
func (p *Package) Add(path string, file File) {
	p.Files[path] = file
}

func (p *Package) AddFile(file string, path string) error {
	f, err := os.Open(file)
	if err != nil {
		return err
	}

	p.Files[path] = f

	return nil
}

//Build the package
func (p *Package) Build() (*bytes.Buffer, error) {
	out := new(bytes.Buffer)
	return out, pkg.Execute(out, p)
}

//Write the build to a file.
func (p *Package) Write(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	buff, err := p.Build()

	if err != nil {
		return err
	}

	_, err = buff.WriteTo(f)
	return err
}

//Template

var pkg *template.Template

func reader(input io.Reader) (string, error) {

	var (
		buff       bytes.Buffer
		err        error
		blockwidth int = 12
		curblock   int = 0
	)

	b := make([]byte, blockwidth)

	for n, err := input.Read(b); err == nil; n, err = input.Read(b) {
		for i := 0; i < n; i++ {
			fmt.Fprintf(&buff, "0x%02x,", b[i])
			curblock++
			if curblock < blockwidth {
				continue
			}
			buff.Write([]byte{'\n'})
			buff.Write([]byte{'\t', '\t'})
			curblock = 0
		}
	}

	return buff.String(), err
}

func init() {

	pkg = template.Must(template.New("file").Funcs(template.FuncMap{"reader": reader}).Parse(` File{
	  data: []byte{
	{{ reader . }}
  },
  fi: FileInfo {
	name:    "{{ .Stat.Name }}",
    size:    {{ .Stat.Size }},
	modTime: time.Unix({{ .Stat.ModTime.Unix }},{{ .Stat.ModTime.UnixNano }}),
    isDir:   {{ .Stat.IsDir }},
  },
}`))

	pkg = template.Must(pkg.New("pkg").Parse(`{{ if .Tag }}// +build {{ .Tag }}

{{ end }}//Generated by github.com/omeid/slurp/resources
package {{ .Pkg }}

import (
  "net/http"
  "time"
  "bytes"
  "os"
  "path/filepath"
  "strings"
  "errors"
)

{{ if .Declare }}
var {{ .Var }} http.FileSystem
{{ end }}

// Helper functions for easier file access.
func Open(name string) (http.File, error) {
	return {{ .Var }}.Open(name)
}

// http.FileSystem implementation.
type FileSystem struct {
	files map[string]File
}

func (fs *FileSystem) Open(name string) (http.File, error) {
	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
	    strings.Contains(name, "\x00") {
		return nil, errors.New("http: invalid character in file path")
	}
	file, ok := fs.files[name]
	if !ok {
		files := []os.FileInfo{}
		for path, file := range fs.files {
			if strings.HasPrefix(path, name) {
				s, _ := file.Stat()
				files = append(files, s)
			}
		}

		if len(files) < 0 {
			return nil, os.ErrNotExist
		}

		//We have a directory.
		return &File{
		  fi: FileInfo{
				isDir: true,
				files: files,
			}}, nil
	}
	file.Reader = bytes.NewReader(file.data)
	return &file, nil
}

type File struct {
	*bytes.Reader
	data []byte
	fi FileInfo
}

// A noop-closer.
func (f *File) Close() error {
	return nil
}

func (f *File) Readdir(count int) ([]os.FileInfo, error) {
  return nil, os.ErrNotExist
}

func (f *File) Stat() (os.FileInfo, error) {
  return &f.fi, nil
}

type FileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	isDir   bool
	sys     interface{}

	files []os.FileInfo
}

func (f *FileInfo) Name() string {
	return f.name
}
func (f *FileInfo) Size() int64 {
	return f.size
}

func (f *FileInfo) Mode() os.FileMode {
	return f.mode
}

func (f *FileInfo) ModTime() time.Time {
	return f.modTime
}

func (f *FileInfo) IsDir() bool {
	return f.isDir
}

func (f *FileInfo) Readdir(count int) ([]os.FileInfo, error) {
	return f.files, nil
}

func (f *FileInfo) Sys() interface{} {
	return f.sys
}

func init() {
  {{ .Var }} = &FileSystem{
		files: map[string]File{
		  {{range $path, $file := .Files }} "/{{ $path }}": {{ template "file" $file }}, {{ end }}
		},
	  }
}
`))
}
<|endoftext|>


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

package globalmetrics

import (
	"testing"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/tsmon"
	"go.chromium.org/luci/resultdb/internal/tasks"
	"go.chromium.org/luci/resultdb/internal/testutil"

	. "github.com/smartystreets/goconvey/convey"
)

func TestUpdateTaskStats(t *testing.T) {
	Convey(`updateTaskStats`, t, func() {
		ctx, _ := tsmon.WithDummyInMemory(testutil.SpannerTestContext(t))
		now := clock.Now(ctx).UTC()

		testutil.MustApply(ctx,
			tasks.Enqueue(tasks.BQExport, "task1", "inv", "payload", now),
			tasks.Enqueue(tasks.BQExport, "task2", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task3", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task4", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task5", "inv", "payload", now),
		)

		Convey(`reports count metric`, func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(taskCountMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, 2)
			So(taskCountMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, 3)

			Convey(`with 0 if there is no task`, func() {
				tasks.Delete(ctx, tasks.BQExport, "task1")
				tasks.Delete(ctx, tasks.BQExport, "task2")

				So(updateTaskStats(ctx), ShouldBeNil)
				So(taskCountMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, 0)
				So(taskCountMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, 3)
			})
		})

		Convey("reports oldest-task-age metric", func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, now.Unix())
			So(oldestTaskMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, now.Unix())
		})

		Convey("stops reporting oldest-task-age, if task-count == 0", func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, now.Unix())
			So(oldestTaskMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, now.Unix())

			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task3")
			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task4")
			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task5")

			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, now.Unix())
			// metric.Get() returns the zero value of the metric value type, if there is
			// no matching cell for the fields. Thus, tsmon.Store.Get() should be used
			// to find whether the cell no longer exists or not.
			fvs := []interface{}{string(tasks.TryFinalizeInvocation)}
			So(tsmon.Store(ctx).Get(ctx, oldestTaskMetric, now, fvs), ShouldBeNil)
		})
	})
}
<commit_msg>[resultdb] fix flaky test in globalmetrics/metrics_test.go<commit_after>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package globalmetrics

import (
	"testing"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/tsmon"
	"go.chromium.org/luci/resultdb/internal/tasks"
	"go.chromium.org/luci/resultdb/internal/testutil"

	. "github.com/smartystreets/goconvey/convey"
)

func TestUpdateTaskStats(t *testing.T) {
	Convey(`updateTaskStats`, t, func() {
		ctx, _ := tsmon.WithDummyInMemory(testutil.SpannerTestContext(t))
		now := clock.Now(ctx).UTC()

		ct := testutil.MustApply(ctx,
			tasks.Enqueue(tasks.BQExport, "task1", "inv", "payload", now),
			tasks.Enqueue(tasks.BQExport, "task2", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task3", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task4", "inv", "payload", now),
			tasks.Enqueue(tasks.TryFinalizeInvocation, "task5", "inv", "payload", now),
		)

		Convey(`reports count metric`, func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(taskCountMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, 2)
			So(taskCountMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, 3)

			Convey(`with 0 if there is no task`, func() {
				tasks.Delete(ctx, tasks.BQExport, "task1")
				tasks.Delete(ctx, tasks.BQExport, "task2")

				So(updateTaskStats(ctx), ShouldBeNil)
				So(taskCountMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, 0)
				So(taskCountMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, 3)
			})
		})

		Convey("reports oldest-task-age metric", func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, ct.Unix())
			So(oldestTaskMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, ct.Unix())
		})

		Convey("stops reporting oldest-task-age, if task-count == 0", func() {
			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, ct.Unix())
			So(oldestTaskMetric.Get(ctx, string(tasks.TryFinalizeInvocation)), ShouldEqual, ct.Unix())

			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task3")
			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task4")
			tasks.Delete(ctx, tasks.TryFinalizeInvocation, "task5")

			So(updateTaskStats(ctx), ShouldBeNil)
			So(oldestTaskMetric.Get(ctx, string(tasks.BQExport)), ShouldEqual, ct.Unix())
			// metric.Get() returns the zero value of the metric value type, if there is
			// no matching cell for the fields. Thus, tsmon.Store.Get() should be used
			// to find whether the cell no longer exists or not.
			fvs := []interface{}{string(tasks.TryFinalizeInvocation)}
			So(tsmon.Store(ctx).Get(ctx, oldestTaskMetric, now, fvs), ShouldBeNil)
		})
	})
}
<|endoftext|>