<commit_before>// Copyright 2020 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 linuxrouting

import (
	"net"
	"runtime"
	"testing"

	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/mac"
	"github.com/vishvananda/netlink"
	"github.com/vishvananda/netns"

	. "gopkg.in/check.v1"
)

func Test(t *testing.T) {
	TestingT(t)
}

type LinuxRoutingSuite struct{}

var _ = Suite(&LinuxRoutingSuite{})

func (e *LinuxRoutingSuite) TestConfigure(c *C) {
	currentNS, err := netns.Get()
	c.Assert(err, IsNil)
	defer func() {
		c.Assert(netns.Set(currentNS), IsNil)
		c.Log("Set back to previous network ns")
	}()

	ip, ri := getFakes(c)
	masterMAC := ri.MasterIfMAC
	runFuncInNetNS(c, func() { runConfigureThenDelete(c, ri, ip, 1500, false) }, masterMAC)
	runFuncInNetNS(c, func() { runConfigureThenDelete(c, ri, ip, 1500, true) }, masterMAC)
}

func (e *LinuxRoutingSuite) TestConfigureRoutewithIncompatibleIP(c *C) {
	_, ri := getFakes(c)
	ipv6 := net.ParseIP("fd00::2").To16()
	c.Assert(ipv6, NotNil)
	err := ri.Configure(ipv6, 1500, true)
	c.Assert(err, NotNil)
	c.Assert(err, ErrorMatches, "IP not compatible")
}

func (e *LinuxRoutingSuite) TestDeleteRoutewithIncompatibleIP(c *C) {
	ipv6 := net.ParseIP("fd00::2").To16()
	c.Assert(ipv6, NotNil)
	err := Delete(ipv6)
	c.Assert(err, NotNil)
	c.Assert(err, ErrorMatches, "IP not compatible")
}

func (e *LinuxRoutingSuite) TestDelete(c *C) {
	fakeIP, fakeRoutingInfo := getFakes(c)
	masterMAC := fakeRoutingInfo.MasterIfMAC

	tests := []struct {
		name    string
		preRun  func() net.IP
		wantErr bool
	}{
		{
			name: "valid IP addr matching rules",
			preRun: func() net.IP {
				runConfigure(c, fakeRoutingInfo, fakeIP, 1500, false)
				return fakeIP
			},
			wantErr: false,
		},
		{
			name: "IP addr doesn't match rules",
			preRun: func() net.IP {
				ip := net.ParseIP("192.168.2.233")
				c.Assert(ip, NotNil)

				runConfigure(c, fakeRoutingInfo, fakeIP, 1500, false)
				return ip
			},
			wantErr: true,
		},
		{
			name: "IP addr matches more than number expected",
			preRun: func() net.IP {
				ip := net.ParseIP("192.168.2.233")
				c.Assert(ip, NotNil)

				runConfigure(c, fakeRoutingInfo, ip, 1500, false)

				// Find interface ingress rules so that we can create a
				// near-duplicate.
				rules, err := route.ListRules(netlink.FAMILY_V4, &route.Rule{
					Priority: linux_defaults.RulePriorityIngress,
				})
				c.Assert(err, IsNil)
				c.Assert(len(rules), Not(Equals), 0)

				// Insert almost duplicate rule; the reason for this is to
				// trigger an error while trying to delete the ingress rule. We
				// are setting the Src because ingress rules don't have
				// one (only Dst), thus we set Src to create a near-duplicate.
				r := rules[0]
				r.Src = &net.IPNet{IP: fakeIP, Mask: net.CIDRMask(32, 32)}
				c.Assert(netlink.RuleAdd(&r), IsNil)

				return ip
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		c.Log(tt.name)
		runFuncInNetNS(c, func() {
			ip := tt.preRun()
			err := Delete(ip)
			c.Assert((err != nil), Equals, tt.wantErr)
		}, masterMAC)
	}
}

func runFuncInNetNS(c *C, run func(), macAddr mac.MAC) {
	// Source:
	// https://github.com/vishvananda/netlink/blob/c79a4b7b40668c3f7867bf256b80b6b2dc65e58e/netns_test.go#L49
	runtime.LockOSThread() // We need a constant OS thread
	defer runtime.UnlockOSThread()

	networkNS, err := netns.New()
	c.Assert(err, IsNil)
	c.Logf("Inside new network ns %v", networkNS.UniqueId())
	defer func() {
		uid := networkNS.UniqueId()
		c.Assert(networkNS.Close(), IsNil)
		c.Logf("Closed new network ns %v", uid)
	}()

	ifaceCleanup := createDummyDevice(c, macAddr)
	defer ifaceCleanup()

	run()
}

func runConfigureThenDelete(c *C, ri RoutingInfo, ip net.IP, mtu int, masq bool) {
	// Create rules and routes
	beforeCreationRules, beforeCreationRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)
	runConfigure(c, ri, ip, mtu, masq)
	afterCreationRules, afterCreationRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)

	c.Assert(len(afterCreationRules), Not(Equals), 0)
	c.Assert(len(afterCreationRoutes), Not(Equals), 0)
	c.Assert(len(beforeCreationRules), Not(Equals), len(afterCreationRules))
	c.Assert(len(beforeCreationRoutes), Not(Equals), len(afterCreationRoutes))

	// Delete rules and routes
	beforeDeletionRules, beforeDeletionRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)
	runDelete(c, ip)
	afterDeletionRules, afterDeletionRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)

	c.Assert(len(beforeDeletionRules), Not(Equals), len(afterDeletionRules))
	c.Assert(len(beforeDeletionRoutes), Not(Equals), len(afterDeletionRoutes))
	c.Assert(len(afterDeletionRules), Equals, len(beforeCreationRules))
	c.Assert(len(afterDeletionRoutes), Equals, len(beforeCreationRoutes))
}

func runConfigure(c *C, ri RoutingInfo, ip net.IP, mtu int, masq bool) {
	err := ri.Configure(ip, mtu, masq)
	c.Assert(err, IsNil)
}

func runDelete(c *C, ip net.IP) {
	err := Delete(ip)
	c.Assert(err, IsNil)
}

// listRulesAndRoutes returns all rules and routes configured on the machine
// this test is running on. Note that this function is intended to be used
// within a network namespace for isolation.
func listRulesAndRoutes(c *C, family int) ([]netlink.Rule, []netlink.Route) {
	rules, err := route.ListRules(family, nil)
	c.Assert(err, IsNil)

	// Rules are created under specific tables, so find the routes that are in
	// those tables.
	var routes []netlink.Route
	for _, r := range rules {
		rr, err := netlink.RouteListFiltered(family, &netlink.Route{
			Table: r.Table,
		}, netlink.RT_FILTER_TABLE)
		c.Assert(err, IsNil)

		routes = append(routes, rr...)
	}

	return rules, routes
}

// createDummyDevice creates a new dummy device with a MAC of `macAddr` to be
// used as a harness in this test. This function returns a function which can
// be used to remove the device for cleanup purposes.
func createDummyDevice(c *C, macAddr mac.MAC) func() {
	if linkExistsWithMAC(c, macAddr) {
		c.Logf("Found device with identical mac addr: %s", macAddr.String())
		c.FailNow()
	}

	dummy := &netlink.Dummy{
		LinkAttrs: netlink.LinkAttrs{
			// NOTE: This name must be less than 16 chars, source:
			// https://elixir.bootlin.com/linux/v5.6/source/include/uapi/linux/if.h#L33
			Name:         "linuxrout-test",
			HardwareAddr: net.HardwareAddr(macAddr),
		},
	}
	err := netlink.LinkAdd(dummy)
	c.Assert(err, IsNil)

	c.Log("Added dummy device")

	found := linkExistsWithMAC(c, macAddr)
	if !found {
		c.Log("Couldn't find device even after creation")
	}
	c.Assert(found, Equals, true)

	return func() {
		c.Assert(netlink.LinkDel(dummy), IsNil)
		c.Log("Cleaned up dummy device")
	}
}

// getFakes returns a fake IP simulating an Endpoint IP and RoutingInfo as test
// harnesses.
func getFakes(c *C) (net.IP, RoutingInfo) {
	fakeGateway := net.ParseIP("192.168.2.1")
	c.Assert(fakeGateway, NotNil)

	_, fakeCIDR, err := net.ParseCIDR("192.168.0.0/16")
	c.Assert(err, IsNil)
	c.Assert(fakeCIDR, NotNil)

	fakeMAC, err := mac.ParseMAC("00:11:22:33:44:55")
	c.Assert(err, IsNil)
	c.Assert(fakeMAC, NotNil)

	fakeRoutingInfo, err := parse(fakeGateway.String(),
		[]string{fakeCIDR.String()},
		fakeMAC.String())
	c.Assert(err, IsNil)
	c.Assert(fakeRoutingInfo, NotNil)

	fakeIP := net.ParseIP("192.168.2.123")
	c.Assert(fakeIP, NotNil)

	return fakeIP, *fakeRoutingInfo
}

func linkExistsWithMAC(c *C, macAddr mac.MAC) bool {
	links, err := netlink.LinkList()
	c.Assert(err, IsNil)

	for _, link := range links {
		if link.Attrs().HardwareAddr.String() == macAddr.String() {
			return true
		}
	}

	return false
}
<commit_msg>linux/routing: Centralize netns handling in test<commit_after>// Copyright 2020 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 linuxrouting

import (
	"net"
	"runtime"
	"testing"

	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/mac"
	"github.com/vishvananda/netlink"
	"github.com/vishvananda/netns"

	. "gopkg.in/check.v1"
)

func Test(t *testing.T) {
	TestingT(t)
}

type LinuxRoutingSuite struct{}

var _ = Suite(&LinuxRoutingSuite{})

func (e *LinuxRoutingSuite) TestConfigure(c *C) {
	ip, ri := getFakes(c)
	masterMAC := ri.MasterIfMAC
	runFuncInNetNS(c, func() { runConfigureThenDelete(c, ri, ip, 1500, false) }, masterMAC)
	runFuncInNetNS(c, func() { runConfigureThenDelete(c, ri, ip, 1500, true) }, masterMAC)
}

func (e *LinuxRoutingSuite) TestConfigureRoutewithIncompatibleIP(c *C) {
	_, ri := getFakes(c)
	ipv6 := net.ParseIP("fd00::2").To16()
	c.Assert(ipv6, NotNil)
	err := ri.Configure(ipv6, 1500, true)
	c.Assert(err, NotNil)
	c.Assert(err, ErrorMatches, "IP not compatible")
}

func (e *LinuxRoutingSuite) TestDeleteRoutewithIncompatibleIP(c *C) {
	ipv6 := net.ParseIP("fd00::2").To16()
	c.Assert(ipv6, NotNil)
	err := Delete(ipv6)
	c.Assert(err, NotNil)
	c.Assert(err, ErrorMatches, "IP not compatible")
}

func (e *LinuxRoutingSuite) TestDelete(c *C) {
	fakeIP, fakeRoutingInfo := getFakes(c)
	masterMAC := fakeRoutingInfo.MasterIfMAC

	tests := []struct {
		name    string
		preRun  func() net.IP
		wantErr bool
	}{
		{
			name: "valid IP addr matching rules",
			preRun: func() net.IP {
				runConfigure(c, fakeRoutingInfo, fakeIP, 1500, false)
				return fakeIP
			},
			wantErr: false,
		},
		{
			name: "IP addr doesn't match rules",
			preRun: func() net.IP {
				ip := net.ParseIP("192.168.2.233")
				c.Assert(ip, NotNil)

				runConfigure(c, fakeRoutingInfo, fakeIP, 1500, false)
				return ip
			},
			wantErr: true,
		},
		{
			name: "IP addr matches more than number expected",
			preRun: func() net.IP {
				ip := net.ParseIP("192.168.2.233")
				c.Assert(ip, NotNil)

				runConfigure(c, fakeRoutingInfo, ip, 1500, false)

				// Find interface ingress rules so that we can create a
				// near-duplicate.
				rules, err := route.ListRules(netlink.FAMILY_V4, &route.Rule{
					Priority: linux_defaults.RulePriorityIngress,
				})
				c.Assert(err, IsNil)
				c.Assert(len(rules), Not(Equals), 0)

				// Insert almost duplicate rule; the reason for this is to
				// trigger an error while trying to delete the ingress rule. We
				// are setting the Src because ingress rules don't have
				// one (only Dst), thus we set Src to create a near-duplicate.
				r := rules[0]
				r.Src = &net.IPNet{IP: fakeIP, Mask: net.CIDRMask(32, 32)}
				c.Assert(netlink.RuleAdd(&r), IsNil)

				return ip
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		c.Log(tt.name)
		runFuncInNetNS(c, func() {
			ip := tt.preRun()
			err := Delete(ip)
			c.Assert((err != nil), Equals, tt.wantErr)
		}, masterMAC)
	}
}

func runFuncInNetNS(c *C, run func(), macAddr mac.MAC) {
	// Source:
	// https://github.com/vishvananda/netlink/blob/c79a4b7b40668c3f7867bf256b80b6b2dc65e58e/netns_test.go#L49
	runtime.LockOSThread() // We need a constant OS thread
	defer runtime.UnlockOSThread()

	currentNS, err := netns.Get()
	c.Assert(err, IsNil)
	c.Logf("Root network ns %v", currentNS.UniqueId())
	defer func() {
		c.Assert(netns.Set(currentNS), IsNil)
		c.Logf("Set back to previous network ns %v", currentNS.UniqueId())
	}()

	networkNS, err := netns.New()
	c.Assert(err, IsNil)
	c.Logf("Inside new network ns %v", networkNS.UniqueId())
	defer func() {
		uid := networkNS.UniqueId()
		c.Assert(networkNS.Close(), IsNil)
		c.Logf("Closed new network ns %v", uid)
	}()

	ifaceCleanup := createDummyDevice(c, macAddr)
	defer ifaceCleanup()

	run()
}

func runConfigureThenDelete(c *C, ri RoutingInfo, ip net.IP, mtu int, masq bool) {
	// Create rules and routes
	beforeCreationRules, beforeCreationRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)
	runConfigure(c, ri, ip, mtu, masq)
	afterCreationRules, afterCreationRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)

	c.Assert(len(afterCreationRules), Not(Equals), 0)
	c.Assert(len(afterCreationRoutes), Not(Equals), 0)
	c.Assert(len(beforeCreationRules), Not(Equals), len(afterCreationRules))
	c.Assert(len(beforeCreationRoutes), Not(Equals), len(afterCreationRoutes))

	// Delete rules and routes
	beforeDeletionRules, beforeDeletionRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)
	runDelete(c, ip)
	afterDeletionRules, afterDeletionRoutes := listRulesAndRoutes(c, netlink.FAMILY_V4)

	c.Assert(len(beforeDeletionRules), Not(Equals), len(afterDeletionRules))
	c.Assert(len(beforeDeletionRoutes), Not(Equals), len(afterDeletionRoutes))
	c.Assert(len(afterDeletionRules), Equals, len(beforeCreationRules))
	c.Assert(len(afterDeletionRoutes), Equals, len(beforeCreationRoutes))
}

func runConfigure(c *C, ri RoutingInfo, ip net.IP, mtu int, masq bool) {
	err := ri.Configure(ip, mtu, masq)
	c.Assert(err, IsNil)
}

func runDelete(c *C, ip net.IP) {
	err := Delete(ip)
	c.Assert(err, IsNil)
}

// listRulesAndRoutes returns all rules and routes configured on the machine
// this test is running on. Note that this function is intended to be used
// within a network namespace for isolation.
func listRulesAndRoutes(c *C, family int) ([]netlink.Rule, []netlink.Route) {
	rules, err := route.ListRules(family, nil)
	c.Assert(err, IsNil)

	// Rules are created under specific tables, so find the routes that are in
	// those tables.
	var routes []netlink.Route
	for _, r := range rules {
		rr, err := netlink.RouteListFiltered(family, &netlink.Route{
			Table: r.Table,
		}, netlink.RT_FILTER_TABLE)
		c.Assert(err, IsNil)

		routes = append(routes, rr...)
	}

	return rules, routes
}

// createDummyDevice creates a new dummy device with a MAC of `macAddr` to be
// used as a harness in this test. This function returns a function which can
// be used to remove the device for cleanup purposes.
func createDummyDevice(c *C, macAddr mac.MAC) func() {
	if linkExistsWithMAC(c, macAddr) {
		c.Logf("Found device with identical mac addr: %s", macAddr.String())
		c.FailNow()
	}

	dummy := &netlink.Dummy{
		LinkAttrs: netlink.LinkAttrs{
			// NOTE: This name must be less than 16 chars, source:
			// https://elixir.bootlin.com/linux/v5.6/source/include/uapi/linux/if.h#L33
			Name:         "linuxrout-test",
			HardwareAddr: net.HardwareAddr(macAddr),
		},
	}
	err := netlink.LinkAdd(dummy)
	c.Assert(err, IsNil)

	c.Log("Added dummy device")

	found := linkExistsWithMAC(c, macAddr)
	if !found {
		c.Log("Couldn't find device even after creation")
	}
	c.Assert(found, Equals, true)

	return func() {
		c.Assert(netlink.LinkDel(dummy), IsNil)
		c.Log("Cleaned up dummy device")
	}
}

// getFakes returns a fake IP simulating an Endpoint IP and RoutingInfo as test
// harnesses.
func getFakes(c *C) (net.IP, RoutingInfo) {
	fakeGateway := net.ParseIP("192.168.2.1")
	c.Assert(fakeGateway, NotNil)

	_, fakeCIDR, err := net.ParseCIDR("192.168.0.0/16")
	c.Assert(err, IsNil)
	c.Assert(fakeCIDR, NotNil)

	fakeMAC, err := mac.ParseMAC("00:11:22:33:44:55")
	c.Assert(err, IsNil)
	c.Assert(fakeMAC, NotNil)

	fakeRoutingInfo, err := parse(fakeGateway.String(),
		[]string{fakeCIDR.String()},
		fakeMAC.String())
	c.Assert(err, IsNil)
	c.Assert(fakeRoutingInfo, NotNil)

	fakeIP := net.ParseIP("192.168.2.123")
	c.Assert(fakeIP, NotNil)

	return fakeIP, *fakeRoutingInfo
}

func linkExistsWithMAC(c *C, macAddr mac.MAC) bool {
	links, err := netlink.LinkList()
	c.Assert(err, IsNil)

	for _, link := range links {
		if link.Attrs().HardwareAddr.String() == macAddr.String() {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>// +build !windows

package filesystem

import (
	"os"
	"runtime"
)

const (
	// maximumReadContentMetadataWorkers provides an upper-bound on the number
	// of Directory.ReadContentMetadata worker Goroutines. It is chosen somewhat
	// arbitrarily, set high enough to allow parallelism but not so high that
	// performance scaling breaks down or that many-core systems are
	// overwhelmed.
	maximumReadContentMetadataWorkers = 4
)

// batchReadContentMetadataRequest is a request type encoding a batch of
// Directory.ReadContentMetadata operations to be performed serially.
type batchReadContentMetadataRequest struct {
	// directory is the directory object for which content metadata reads should
	// be performed.
	directory *Directory
	// names are the names of entries in the directory for which metadata should
	// be read.
	names []string
	// results is the pre-allocated storage space into which results should be
	// placed. It must have a length equal to name. In the case of entries that
	// are missing (i.e. those for which the fstatat operation returns an
	// os.IsNotExist error), a nil pointer should be stored.
	results []*Metadata
}

// batchReadContentMetadataResponse is a response type encoding the outcome of a
// batchReadContentMetadataRequest.
type batchReadContentMetadataResponse struct {
	// sawMissingEntry indicates whether or not any of the operations in the
	// batch saw an missing entry and set a corresponding nil pointer in the
	// results.
	sawMissingEntry bool
	// fatalError stores any fatal error (i.e. something other than an
	// os.IsNotExist error) that occurred for the batch. In this case, results
	// should be considered invalid.
	fatalError error
}

// handleBatchReadContentMetadataRequests is the entry point for
// ReadContentMetadata worker Goroutines. It processes requests until the
// requests channel is closed, at which point it closes its responses channel.
func handleBatchReadContentMetadataRequests(
	requests chan batchReadContentMetadataRequest,
	responses chan batchReadContentMetadataResponse,
) {
	// Loop until the requests channel is closed.
	for request := range requests {
		// Track missing entries and errors.
		var sawMissingEntry bool
		var fatalError error

		// Process each name in the request.
		for n, name := range request.names {
			if m, err := request.directory.ReadContentMetadata(name); err != nil {
				if os.IsNotExist(err) {
					request.results[n] = nil
					sawMissingEntry = true
					continue
				} else {
					fatalError = err
					break
				}
			} else {
				request.results[n] = m
			}
		}

		// Send a response.
		responses <- batchReadContentMetadataResponse{
			sawMissingEntry: sawMissingEntry,
			fatalError:      fatalError,
		}
	}

	// Indicate completion by closing our responses channel.
	close(responses)
}

// parallelReadContentMetadataRequest is a request type encoding a batch of
// Directory.ReadContentMetadata operations to be performed in parallel.
type parallelReadContentMetadataRequest struct {
	// directory is the directory object for which content metadata reads should
	// be performed.
	directory *Directory
	// names are the names of entries in the directory for which metadata should
	// be read.
	names []string
}

// parallelReadContentMetadataResponse is a response type encoding the outcome
// of a parallelReadContentMetadataRequest.
type parallelReadContentMetadataResponse struct {
	// results are the results for the request. They may be fewer than the names
	// in the corresponding request since entries which don't exist are ignored.
	results []*Metadata
	// fatalError stores any fatal error (i.e. something other than an
	// os.IsNotExist error) that occurred when processing the request. In this
	// case, results should be considered invalid.
	fatalError error
}

// parallelReadContentMetadataRequests is a queue for parallel
// Directory.ReadContentMetadata operations. Once a write to this channel
// succeeds, the writer knows that it should expect a response on
// parallelReadContentMetadataResponses.
var parallelReadContentMetadataRequests = make(chan parallelReadContentMetadataRequest)

// parallelReadContentMetadataResponses is the result channel for requests
// submitted to parallelReadContentMetadataRequests.
var parallelReadContentMetadataResponses = make(chan parallelReadContentMetadataResponse)

// handleParallelReadContentMetadataRequests is the entry point for parallel
// ReadContentMetadata request handling.
func handleParallelReadContentMetadataRequests() {
	// Compute the worker count.
	workerCount := runtime.NumCPU()
	if workerCount > maximumReadContentMetadataWorkers {
		workerCount = maximumReadContentMetadataWorkers
	}

	// Create request/response channels and start workers.
	batchRequestQueues := make([]chan batchReadContentMetadataRequest, workerCount)
	batchResponseQueues := make([]chan batchReadContentMetadataResponse, workerCount)
	for w := 0; w < workerCount; w++ {
		batchRequestQueues[w] = make(chan batchReadContentMetadataRequest, 1)
		batchResponseQueues[w] = make(chan batchReadContentMetadataResponse, 1)
		go handleBatchReadContentMetadataRequests(
			batchRequestQueues[w],
			batchResponseQueues[w],
		)
	}

	// Handle requests indefinitely.
	for request := range parallelReadContentMetadataRequests {
		// Allocate result storage.
		results := make([]*Metadata, len(request.names))

		// Compute the batch size. The additive term in the numerator of this
		// formula ensures that batchSize * workerCount >= len(names), while
		// still giving ~equal distribution across workers. Because of this
		// additive term, we have to check explicitly in the dispatch loop below
		// that we don't exceed the boundaries of the name slice.
		batchSize := (len(request.names) + (workerCount - 1)) / workerCount

		// Dispatch batch requests and track how many workers we use.
		workersUsed := 0
		for batchStart := 0; batchStart < len(request.names); batchStart += batchSize {
			batchStop := batchStart + batchSize
			if batchStop > len(request.names) {
				batchStop = len(request.names)
			}
			batchRequestQueues[workersUsed] <- batchReadContentMetadataRequest{
				directory: request.directory,
				names:     request.names[batchStart:batchStop],
				results:   results[batchStart:batchStop],
			}
			workersUsed++
		}

		// Wait for responses from all of the workers that we used.
		var fatalError error
		var sawMissingEntry bool
		for _, responseQueue := range batchResponseQueues[:workersUsed] {
			response := <-responseQueue
			sawMissingEntry = sawMissingEntry || response.sawMissingEntry
			if response.fatalError != nil && fatalError == nil {
				fatalError = response.fatalError
			}
		}

		// Handle the error case.
		if fatalError != nil {
			parallelReadContentMetadataResponses <- parallelReadContentMetadataResponse{
				fatalError: fatalError,
			}
			continue
		}

		// If missing entries were detected, shift them out of the slice.
		if sawMissingEntry {
			filteredResults := results[:0]
			for _, r := range results {
				if r != nil {
					filteredResults = append(filteredResults, r)
				}
			}
			results = filteredResults
		}

		// Success.
		parallelReadContentMetadataResponses <- parallelReadContentMetadataResponse{
			results: results,
		}
	}
}

// parallelMetadataDisabled tracks whether or not parallel metadata query
// operations have been disabled.
var parallelMetadataDisabled bool

func init() {
	// Disable parallel metadata query operations if this platform isn't
	// whitelisted for support. For now this whitelist is just the set of POSIX
	// platforms for which we run tests, because POSIX doesn't explicitly
	// guarantee that fstatat is safe for concurrent invocation on the same file
	// descriptor and we require confirmation of this behavior through
	// observation. We'll expand this list as we bring more builders online and
	// this code has more shakedown time.
	parallelMetadataDisabled = !(runtime.GOOS == "linux" || runtime.GOOS == "darwin")

	// Disable parallel metadata query operations if we don't have multiple
	// CPUs, because for a single-core system it only adds overhead.
	if runtime.NumCPU() < 2 {
		parallelMetadataDisabled = true
	}

	// Temporarily disable parallel metadata query operations on all platforms
	// until later in the v0.9.0 release cycle.
	parallelMetadataDisabled = true

	// If parallel metadata query operations aren't disabled, then start the
	// worker Goroutines.
	if !parallelMetadataDisabled {
		go handleParallelReadContentMetadataRequests()
	}
}
<commit_msg>Revert "Temporarily disabled parallel metadata query operations."<commit_after>// +build !windows

package filesystem

import (
	"os"
	"runtime"
)

const (
	// maximumReadContentMetadataWorkers provides an upper-bound on the number
	// of Directory.ReadContentMetadata worker Goroutines. It is chosen somewhat
	// arbitrarily, set high enough to allow parallelism but not so high that
	// performance scaling breaks down or that many-core systems are
	// overwhelmed.
	maximumReadContentMetadataWorkers = 4
)

// batchReadContentMetadataRequest is a request type encoding a batch of
// Directory.ReadContentMetadata operations to be performed serially.
type batchReadContentMetadataRequest struct {
	// directory is the directory object for which content metadata reads should
	// be performed.
	directory *Directory
	// names are the names of entries in the directory for which metadata should
	// be read.
	names []string
	// results is the pre-allocated storage space into which results should be
	// placed. It must have a length equal to name. In the case of entries that
	// are missing (i.e. those for which the fstatat operation returns an
	// os.IsNotExist error), a nil pointer should be stored.
	results []*Metadata
}

// batchReadContentMetadataResponse is a response type encoding the outcome of a
// batchReadContentMetadataRequest.
type batchReadContentMetadataResponse struct {
	// sawMissingEntry indicates whether or not any of the operations in the
	// batch saw an missing entry and set a corresponding nil pointer in the
	// results.
	sawMissingEntry bool
	// fatalError stores any fatal error (i.e. something other than an
	// os.IsNotExist error) that occurred for the batch. In this case, results
	// should be considered invalid.
	fatalError error
}

// handleBatchReadContentMetadataRequests is the entry point for
// ReadContentMetadata worker Goroutines. It processes requests until the
// requests channel is closed, at which point it closes its responses channel.
func handleBatchReadContentMetadataRequests(
	requests chan batchReadContentMetadataRequest,
	responses chan batchReadContentMetadataResponse,
) {
	// Loop until the requests channel is closed.
	for request := range requests {
		// Track missing entries and errors.
		var sawMissingEntry bool
		var fatalError error

		// Process each name in the request.
		for n, name := range request.names {
			if m, err := request.directory.ReadContentMetadata(name); err != nil {
				if os.IsNotExist(err) {
					request.results[n] = nil
					sawMissingEntry = true
					continue
				} else {
					fatalError = err
					break
				}
			} else {
				request.results[n] = m
			}
		}

		// Send a response.
		responses <- batchReadContentMetadataResponse{
			sawMissingEntry: sawMissingEntry,
			fatalError:      fatalError,
		}
	}

	// Indicate completion by closing our responses channel.
	close(responses)
}

// parallelReadContentMetadataRequest is a request type encoding a batch of
// Directory.ReadContentMetadata operations to be performed in parallel.
type parallelReadContentMetadataRequest struct {
	// directory is the directory object for which content metadata reads should
	// be performed.
	directory *Directory
	// names are the names of entries in the directory for which metadata should
	// be read.
	names []string
}

// parallelReadContentMetadataResponse is a response type encoding the outcome
// of a parallelReadContentMetadataRequest.
type parallelReadContentMetadataResponse struct {
	// results are the results for the request. They may be fewer than the names
	// in the corresponding request since entries which don't exist are ignored.
	results []*Metadata
	// fatalError stores any fatal error (i.e. something other than an
	// os.IsNotExist error) that occurred when processing the request. In this
	// case, results should be considered invalid.
	fatalError error
}

// parallelReadContentMetadataRequests is a queue for parallel
// Directory.ReadContentMetadata operations. Once a write to this channel
// succeeds, the writer knows that it should expect a response on
// parallelReadContentMetadataResponses.
var parallelReadContentMetadataRequests = make(chan parallelReadContentMetadataRequest)

// parallelReadContentMetadataResponses is the result channel for requests
// submitted to parallelReadContentMetadataRequests.
var parallelReadContentMetadataResponses = make(chan parallelReadContentMetadataResponse)

// handleParallelReadContentMetadataRequests is the entry point for parallel
// ReadContentMetadata request handling.
func handleParallelReadContentMetadataRequests() {
	// Compute the worker count.
	workerCount := runtime.NumCPU()
	if workerCount > maximumReadContentMetadataWorkers {
		workerCount = maximumReadContentMetadataWorkers
	}

	// Create request/response channels and start workers.
	batchRequestQueues := make([]chan batchReadContentMetadataRequest, workerCount)
	batchResponseQueues := make([]chan batchReadContentMetadataResponse, workerCount)
	for w := 0; w < workerCount; w++ {
		batchRequestQueues[w] = make(chan batchReadContentMetadataRequest, 1)
		batchResponseQueues[w] = make(chan batchReadContentMetadataResponse, 1)
		go handleBatchReadContentMetadataRequests(
			batchRequestQueues[w],
			batchResponseQueues[w],
		)
	}

	// Handle requests indefinitely.
	for request := range parallelReadContentMetadataRequests {
		// Allocate result storage.
		results := make([]*Metadata, len(request.names))

		// Compute the batch size. The additive term in the numerator of this
		// formula ensures that batchSize * workerCount >= len(names), while
		// still giving ~equal distribution across workers. Because of this
		// additive term, we have to check explicitly in the dispatch loop below
		// that we don't exceed the boundaries of the name slice.
		batchSize := (len(request.names) + (workerCount - 1)) / workerCount

		// Dispatch batch requests and track how many workers we use.
		workersUsed := 0
		for batchStart := 0; batchStart < len(request.names); batchStart += batchSize {
			batchStop := batchStart + batchSize
			if batchStop > len(request.names) {
				batchStop = len(request.names)
			}
			batchRequestQueues[workersUsed] <- batchReadContentMetadataRequest{
				directory: request.directory,
				names:     request.names[batchStart:batchStop],
				results:   results[batchStart:batchStop],
			}
			workersUsed++
		}

		// Wait for responses from all of the workers that we used.
		var fatalError error
		var sawMissingEntry bool
		for _, responseQueue := range batchResponseQueues[:workersUsed] {
			response := <-responseQueue
			sawMissingEntry = sawMissingEntry || response.sawMissingEntry
			if response.fatalError != nil && fatalError == nil {
				fatalError = response.fatalError
			}
		}

		// Handle the error case.
		if fatalError != nil {
			parallelReadContentMetadataResponses <- parallelReadContentMetadataResponse{
				fatalError: fatalError,
			}
			continue
		}

		// If missing entries were detected, shift them out of the slice.
		if sawMissingEntry {
			filteredResults := results[:0]
			for _, r := range results {
				if r != nil {
					filteredResults = append(filteredResults, r)
				}
			}
			results = filteredResults
		}

		// Success.
		parallelReadContentMetadataResponses <- parallelReadContentMetadataResponse{
			results: results,
		}
	}
}

// parallelMetadataDisabled tracks whether or not parallel metadata query
// operations have been disabled.
var parallelMetadataDisabled bool

func init() {
	// Disable parallel metadata query operations if this platform isn't
	// whitelisted for support. For now this whitelist is just the set of POSIX
	// platforms for which we run tests, because POSIX doesn't explicitly
	// guarantee that fstatat is safe for concurrent invocation on the same file
	// descriptor and we require confirmation of this behavior through
	// observation. We'll expand this list as we bring more builders online and
	// this code has more shakedown time.
	parallelMetadataDisabled = !(runtime.GOOS == "linux" || runtime.GOOS == "darwin")

	// Disable parallel metadata query operations if we don't have multiple
	// CPUs, because for a single-core system it only adds overhead.
	if runtime.NumCPU() < 2 {
		parallelMetadataDisabled = true
	}

	// If parallel metadata query operations aren't disabled, then start the
	// worker Goroutines.
	if !parallelMetadataDisabled {
		go handleParallelReadContentMetadataRequests()
	}
}
<|endoftext|>


<commit_before>package analysis

import (
	"fmt"

	"github.com/gonum/graph"

	osgraph "github.com/openshift/origin/pkg/oc/graph/genericgraph"
	kubeedges "github.com/openshift/origin/pkg/oc/graph/kubegraph"
	kubegraph "github.com/openshift/origin/pkg/oc/graph/kubegraph/nodes"
)

const (
	UnmountableSecretWarning    = "UnmountableSecret"
	MissingSecretWarning        = "MissingSecret"
	MissingLivenessProbeWarning = "MissingLivenessProbe"
)

// FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount
func FindUnmountableSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		unmountableSecrets := CheckForUnmountableSecrets(g, podSpecNode)

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
		topLevelString := f.ResourceName(topLevelNode)

		saString := "MISSING_SA"
		saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind)
		if len(saNodes) > 0 {
			saString = f.ResourceName(saNodes[0])
		}

		for _, unmountableSecret := range unmountableSecrets {
			markers = append(markers, osgraph.Marker{
				Node:         podSpecNode,
				RelatedNodes: []graph.Node{unmountableSecret},

				Severity: osgraph.WarningSeverity,
				Key:      UnmountableSecretWarning,
				Message: fmt.Sprintf("%s is attempting to mount a secret %s disallowed by %s",
					topLevelString, f.ResourceName(unmountableSecret), saString),
			})
		}
	}

	return markers
}

// FindMissingSecrets inspects all PodSpecs for any Secret reference that is a synthetic node (not a pre-existing node in the graph)
func FindMissingSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		missingSecrets := CheckMissingMountedSecrets(g, podSpecNode)

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
		topLevelString := f.ResourceName(topLevelNode)

		for _, missingSecret := range missingSecrets {
			markers = append(markers, osgraph.Marker{
				Node:         podSpecNode,
				RelatedNodes: []graph.Node{missingSecret},

				Severity: osgraph.WarningSeverity,
				Key:      UnmountableSecretWarning,
				Message: fmt.Sprintf("%s is attempting to mount a missing secret %s",
					topLevelString, f.ResourceName(missingSecret)),
			})
		}
	}

	return markers
}

// FindMissingLivenessProbes inspects all PodSpecs for missing liveness probes and generates a list of non-duplicate markers
func FindMissingLivenessProbes(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		if hasLivenessProbe(podSpecNode) {
			continue
		}

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)

		// skip any podSpec nodes that are managed by other nodes.
		// Liveness probes should only be applied to a controlling
		// podSpec node, and not to any of its children.
		if hasControllerRefEdge(g, topLevelNode) {
			continue
		}

		topLevelString := f.ResourceName(topLevelNode)
		markers = append(markers, osgraph.Marker{
			Node:         podSpecNode,
			RelatedNodes: []graph.Node{topLevelNode},

			Severity: osgraph.InfoSeverity,
			Key:      MissingLivenessProbeWarning,
			Message: fmt.Sprintf("%s has no liveness probe to verify pods are still running.",
				topLevelString),
			Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --liveness ...", setProbeCommand, topLevelString)),
		})
	}

	return markers
}

// hasLivenessProbe iterates through all of the containers in a podSpecNode returning true
// if at least one container has a liveness probe, or false otherwise
func hasLivenessProbe(podSpecNode *kubegraph.PodSpecNode) bool {
	for _, container := range podSpecNode.PodSpec.Containers {
		if container.LivenessProbe != nil {
			return true
		}
	}
	return false
}

// hasControllerRefEdge returns true if a given node contains one or more "ManagedByController" outbound edges
func hasControllerRefEdge(g osgraph.Graph, node graph.Node) bool {
	managedEdges := g.OutboundEdges(node, kubeedges.ManagedByControllerEdgeKind)
	return len(managedEdges) > 0
}

// CheckForUnmountableSecrets checks to be sure that all the referenced secrets are mountable (by service account)
func CheckForUnmountableSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
	saNodes := g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.ServiceAccountNodeKind, kubeedges.ReferencedServiceAccountEdgeKind)
	saMountableSecrets := []*kubegraph.SecretNode{}

	if len(saNodes) > 0 {
		saNode := saNodes[0].(*kubegraph.ServiceAccountNode)
		for _, secretNode := range g.SuccessorNodesByNodeAndEdgeKind(saNode, kubegraph.SecretNodeKind, kubeedges.MountableSecretEdgeKind) {
			saMountableSecrets = append(saMountableSecrets, secretNode.(*kubegraph.SecretNode))
		}
	}

	unmountableSecrets := []*kubegraph.SecretNode{}

	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)

		mountable := false
		for _, mountableSecretNode := range saMountableSecrets {
			if mountableSecretNode == mountedSecretNode {
				mountable = true
				break
			}
		}

		if !mountable {
			unmountableSecrets = append(unmountableSecrets, mountedSecretNode)
			continue
		}
	}

	return unmountableSecrets
}

// CheckMissingMountedSecrets checks to be sure that all the referenced secrets are present (not synthetic)
func CheckMissingMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
	missingSecrets := []*kubegraph.SecretNode{}

	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
		if !mountedSecretNode.Found() {
			missingSecrets = append(missingSecrets, mountedSecretNode)
		}
	}

	return missingSecrets
}
<commit_msg>cli: do not suggest liveness probes for controller owned pods<commit_after>package analysis

import (
	"fmt"

	"github.com/gonum/graph"

	osgraph "github.com/openshift/origin/pkg/oc/graph/genericgraph"
	kubeedges "github.com/openshift/origin/pkg/oc/graph/kubegraph"
	kubegraph "github.com/openshift/origin/pkg/oc/graph/kubegraph/nodes"
)

const (
	UnmountableSecretWarning    = "UnmountableSecret"
	MissingSecretWarning        = "MissingSecret"
	MissingLivenessProbeWarning = "MissingLivenessProbe"
)

// FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount
func FindUnmountableSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		unmountableSecrets := CheckForUnmountableSecrets(g, podSpecNode)

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
		topLevelString := f.ResourceName(topLevelNode)

		saString := "MISSING_SA"
		saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind)
		if len(saNodes) > 0 {
			saString = f.ResourceName(saNodes[0])
		}

		for _, unmountableSecret := range unmountableSecrets {
			markers = append(markers, osgraph.Marker{
				Node:         podSpecNode,
				RelatedNodes: []graph.Node{unmountableSecret},

				Severity: osgraph.WarningSeverity,
				Key:      UnmountableSecretWarning,
				Message: fmt.Sprintf("%s is attempting to mount a secret %s disallowed by %s",
					topLevelString, f.ResourceName(unmountableSecret), saString),
			})
		}
	}

	return markers
}

// FindMissingSecrets inspects all PodSpecs for any Secret reference that is a synthetic node (not a pre-existing node in the graph)
func FindMissingSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		missingSecrets := CheckMissingMountedSecrets(g, podSpecNode)

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
		topLevelString := f.ResourceName(topLevelNode)

		for _, missingSecret := range missingSecrets {
			markers = append(markers, osgraph.Marker{
				Node:         podSpecNode,
				RelatedNodes: []graph.Node{missingSecret},

				Severity: osgraph.WarningSeverity,
				Key:      UnmountableSecretWarning,
				Message: fmt.Sprintf("%s is attempting to mount a missing secret %s",
					topLevelString, f.ResourceName(missingSecret)),
			})
		}
	}

	return markers
}

// FindMissingLivenessProbes inspects all PodSpecs for missing liveness probes and generates a list of non-duplicate markers
func FindMissingLivenessProbes(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		if hasLivenessProbe(podSpecNode) {
			continue
		}

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)

		// skip any podSpec nodes that are managed by other nodes.
		// Liveness probes should only be applied to a controlling
		// podSpec node, and not to any of its children.
		if hasControllerRefEdge(g, topLevelNode) {
			continue
		}

		// skip any podSpec nodes that belong to Pods with controller
		// owner references.
		if hasControllerOwnerReference(topLevelNode) {
			continue
		}

		topLevelString := f.ResourceName(topLevelNode)
		markers = append(markers, osgraph.Marker{
			Node:         podSpecNode,
			RelatedNodes: []graph.Node{topLevelNode},

			Severity: osgraph.InfoSeverity,
			Key:      MissingLivenessProbeWarning,
			Message: fmt.Sprintf("%s has no liveness probe to verify pods are still running.",
				topLevelString),
			Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --liveness ...", setProbeCommand, topLevelString)),
		})
	}

	return markers
}

// hasLivenessProbe iterates through all of the containers in a podSpecNode returning true
// if at least one container has a liveness probe, or false otherwise
func hasLivenessProbe(podSpecNode *kubegraph.PodSpecNode) bool {
	for _, container := range podSpecNode.PodSpec.Containers {
		if container.LivenessProbe != nil {
			return true
		}
	}
	return false
}

// hasControllerOwnerReference returns true if a given Pod node is controller controlled.
func hasControllerOwnerReference(node graph.Node) bool {
	pod, ok := node.(*kubegraph.PodNode)
	if !ok {
		return false
	}
	for _, ref := range pod.OwnerReferences {
		if ref.Controller != nil && *ref.Controller == true {
			return true
		}
	}
	return false
}

// hasControllerRefEdge returns true if a given node contains one or more "ManagedByController" outbound edges
func hasControllerRefEdge(g osgraph.Graph, node graph.Node) bool {
	managedEdges := g.OutboundEdges(node, kubeedges.ManagedByControllerEdgeKind)
	return len(managedEdges) > 0
}

// CheckForUnmountableSecrets checks to be sure that all the referenced secrets are mountable (by service account)
func CheckForUnmountableSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
	saNodes := g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.ServiceAccountNodeKind, kubeedges.ReferencedServiceAccountEdgeKind)
	saMountableSecrets := []*kubegraph.SecretNode{}

	if len(saNodes) > 0 {
		saNode := saNodes[0].(*kubegraph.ServiceAccountNode)
		for _, secretNode := range g.SuccessorNodesByNodeAndEdgeKind(saNode, kubegraph.SecretNodeKind, kubeedges.MountableSecretEdgeKind) {
			saMountableSecrets = append(saMountableSecrets, secretNode.(*kubegraph.SecretNode))
		}
	}

	unmountableSecrets := []*kubegraph.SecretNode{}

	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)

		mountable := false
		for _, mountableSecretNode := range saMountableSecrets {
			if mountableSecretNode == mountedSecretNode {
				mountable = true
				break
			}
		}

		if !mountable {
			unmountableSecrets = append(unmountableSecrets, mountedSecretNode)
			continue
		}
	}

	return unmountableSecrets
}

// CheckMissingMountedSecrets checks to be sure that all the referenced secrets are present (not synthetic)
func CheckMissingMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
	missingSecrets := []*kubegraph.SecretNode{}

	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
		if !mountedSecretNode.Found() {
			missingSecrets = append(missingSecrets, mountedSecretNode)
		}
	}

	return missingSecrets
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package rest

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestInputStreamReader(t *testing.T) {
	resultString := "Test output"
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Write([]byte(resultString))
	}))
	defer s.Close()
	u, err := url.Parse(s.URL)
	if err != nil {
		t.Errorf("Error parsing server URL: %v", err)
		return
	}
	streamer := &LocationStreamer{
		Location: u,
	}
	readCloser, _, _, err := streamer.InputStream("v1beta1", "text/plain")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	result, err := ioutil.ReadAll(readCloser)
	if string(result) != resultString {
		t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), resultString)
	}
}

func TestInputStreamNullLocation(t *testing.T) {
	streamer := &LocationStreamer{
		Location: nil,
	}
	readCloser, _, _, err := streamer.InputStream("v1beta1", "text/plain")
	if err != nil {
		t.Errorf("Unexpected error when getting stream with null location: %v", err)
	}
	if readCloser != nil {
		t.Errorf("Expected stream to be nil. Got: %#v", readCloser)
	}
}

type testTransport struct {
	body string
	err  error
}

func (tt *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	r := bufio.NewReader(bytes.NewBufferString(tt.body))
	return http.ReadResponse(r, req)
}

func fakeTransport(mime, message string) http.RoundTripper {
	content := fmt.Sprintf("HTTP/1.1 200 OK\nContent-Type: %s\n\n%s", mime, message)
	return &testTransport{body: content}
}

func TestInputStreamContentType(t *testing.T) {
	location, _ := url.Parse("http://www.example.com")
	streamer := &LocationStreamer{
		Location:  location,
		Transport: fakeTransport("application/json", "hello world"),
	}
	readCloser, _, contentType, err := streamer.InputStream("v1beta1", "text/plain")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	if contentType != "application/json" {
		t.Errorf("Unexpected content type. Got: %s. Expected: application/json", contentType)
	}
}

func TestInputStreamTransport(t *testing.T) {
	message := "hello world"
	location, _ := url.Parse("http://www.example.com")
	streamer := &LocationStreamer{
		Location:  location,
		Transport: fakeTransport("text/plain", message),
	}
	readCloser, _, _, err := streamer.InputStream("v1beta1", "text/plain")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	result, err := ioutil.ReadAll(readCloser)
	if string(result) != message {
		t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), message)
	}
}
<commit_msg>remove string "v1beta1" from streamer_test.go<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package rest

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestInputStreamReader(t *testing.T) {
	resultString := "Test output"
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Write([]byte(resultString))
	}))
	defer s.Close()
	u, err := url.Parse(s.URL)
	if err != nil {
		t.Errorf("Error parsing server URL: %v", err)
		return
	}
	streamer := &LocationStreamer{
		Location: u,
	}
	readCloser, _, _, err := streamer.InputStream("", "")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	result, err := ioutil.ReadAll(readCloser)
	if string(result) != resultString {
		t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), resultString)
	}
}

func TestInputStreamNullLocation(t *testing.T) {
	streamer := &LocationStreamer{
		Location: nil,
	}
	readCloser, _, _, err := streamer.InputStream("", "")
	if err != nil {
		t.Errorf("Unexpected error when getting stream with null location: %v", err)
	}
	if readCloser != nil {
		t.Errorf("Expected stream to be nil. Got: %#v", readCloser)
	}
}

type testTransport struct {
	body string
	err  error
}

func (tt *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	r := bufio.NewReader(bytes.NewBufferString(tt.body))
	return http.ReadResponse(r, req)
}

func fakeTransport(mime, message string) http.RoundTripper {
	content := fmt.Sprintf("HTTP/1.1 200 OK\nContent-Type: %s\n\n%s", mime, message)
	return &testTransport{body: content}
}

func TestInputStreamContentType(t *testing.T) {
	location, _ := url.Parse("http://www.example.com")
	streamer := &LocationStreamer{
		Location:  location,
		Transport: fakeTransport("application/json", "hello world"),
	}
	readCloser, _, contentType, err := streamer.InputStream("", "")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	if contentType != "application/json" {
		t.Errorf("Unexpected content type. Got: %s. Expected: application/json", contentType)
	}
}

func TestInputStreamTransport(t *testing.T) {
	message := "hello world"
	location, _ := url.Parse("http://www.example.com")
	streamer := &LocationStreamer{
		Location:  location,
		Transport: fakeTransport("text/plain", message),
	}
	readCloser, _, _, err := streamer.InputStream("", "")
	if err != nil {
		t.Errorf("Unexpected error when getting stream: %v", err)
		return
	}
	defer readCloser.Close()
	result, err := ioutil.ReadAll(readCloser)
	if string(result) != message {
		t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), message)
	}
}
<|endoftext|>


<commit_before>package mctraining

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

	"github.com/codegangsta/cli"
	r "github.com/dancannon/gorethink"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/db/model"
	"github.com/materials-commons/mcstore/pkg/db/schema"
)

var (
	createProjectCommand = cli.Command{
		Name:    "project",
		Aliases: []string{"proj", "p"},
		Usage:   "Create a new project",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "template, t",
				Value: "training_template",
				Usage: "The template project to use",
			},
			cli.StringFlag{
				Name:  "owner",
				Usage: "Project owner",
			},
		},
		Action: createProjectCLI,
	}

	rusers = dai.NewRUsers(session)
	rprojs = dai.NewRProjects(session)
	rfiles = dai.NewRFiles(session)
)

func createProjectCLI(c *cli.Context) {
	if len(c.Args()) != 1 {
		fmt.Println("You must provide a project name")
		os.Exit(1)
	}

	projectName := c.Args()[0]
	owner := c.String("owner")
	if owner == "" {
		owner = projectName + "@mctraining.org"
	}

	templateProjectID := c.String("template")

	createUser(owner)
	projectID := createProject(projectName, owner)
	addFilesFromTemplateProject(templateProjectID, projectID, owner)
}

func createUser(user string) {
	if _, err := rusers.ByID(user); err != nil {
		// User not found so create a new one
		runMCUser(user)
	}

}

func runMCUser(user string) {
	out, err := exec.Command("mcuser.py", "--email="+user, "--password='"+user+"'").Output()
	if err != nil {
		fmt.Println("Unable to add user:", err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

func createProject(name, owner string) string {
	proj := schema.NewProject(name, owner)
	p, err := rprojs.Insert(&proj)
	if err != nil {
		fmt.Printf("Unable to create project %s: %s\n", name, err)
		os.Exit(1)
	}
	return p.ID
}

func addFilesFromTemplateProject(templateProjectID, projectID, owner string) {
	projectDirID := getProjectDirID(projectID)
	rql := r.Table("project2datafile").GetAllByIndex("project_id", templateProjectID)
	var projectFiles []schema.Project2DataFile
	if err := model.ProjectFiles.Qs(session).Rows(rql, &projectFiles); err != nil {
		fmt.Printf("Unable to get files for template project %s: %s\n", templateProjectID, err)
		os.Exit(1)
	}

	for _, p2f := range projectFiles {
		f, err := rfiles.ByID(p2f.DataFileID)
		if err != nil {
			fmt.Printf("Unable to retrieve file %s for project %s: %s\n", p2f.DataFileID, projectID, err)
		}
		f.UsesID = f.ID
		f.ID = ""
		f.Owner = owner
		if _, err := rfiles.Insert(f, projectDirID, projectID); err != nil {
			fmt.Printf("Failed to insert file %s into project %s: %s\n", f.Name, projectID, err)
		}
	}
}

func getProjectDirID(projectID string) string {
	var projectDir schema.Project2DataDir
	rql := r.Table("project2datadir").GetAllByIndex("project_id", projectID)
	if err := model.GetRow(rql, session, &projectDir); err != nil {
		fmt.Printf("Unable to get project directory for %s: %s\n", projectID, err)
		os.Exit(1)
	}

	return projectDir.DataDirID
}
<commit_msg>Make sure mcuser.py is in path.<commit_after>package mctraining

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

	"github.com/codegangsta/cli"
	r "github.com/dancannon/gorethink"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/db/model"
	"github.com/materials-commons/mcstore/pkg/db/schema"
)

var (
	createProjectCommand = cli.Command{
		Name:    "project",
		Aliases: []string{"proj", "p"},
		Usage:   "Create a new project",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "template, t",
				Value: "training_template",
				Usage: "The template project to use",
			},
			cli.StringFlag{
				Name:  "owner",
				Usage: "Project owner",
			},
		},
		Action: createProjectCLI,
	}

	rusers = dai.NewRUsers(session)
	rprojs = dai.NewRProjects(session)
	rfiles = dai.NewRFiles(session)
)

func createProjectCLI(c *cli.Context) {
	if len(c.Args()) != 1 {
		fmt.Println("You must provide a project name")
		os.Exit(1)
	}

	path, err := exec.LookPath("mcuser.py")
	if err != nil {
		fmt.Println("Unable to find mcuser.py in your path.")
		os.Exit(1)
	}

	fmt.Println("Using mcuser.py at:", path)

	projectName := c.Args()[0]
	owner := c.String("owner")
	if owner == "" {
		owner = projectName + "@mctraining.org"
	}

	templateProjectID := c.String("template")

	createUser(owner)
	projectID := createProject(projectName, owner)
	addFilesFromTemplateProject(templateProjectID, projectID, owner)
}

func createUser(user string) {
	if _, err := rusers.ByID(user); err != nil {
		// User not found so create a new one
		runMCUser(user)
	}

}

func runMCUser(user string) {
	out, err := exec.Command("mcuser.py", "--email="+user, "--password='"+user+"'").Output()
	if err != nil {
		fmt.Println("Unable to add user:", err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

func createProject(name, owner string) string {
	proj := schema.NewProject(name, owner)
	p, err := rprojs.Insert(&proj)
	if err != nil {
		fmt.Printf("Unable to create project %s: %s\n", name, err)
		os.Exit(1)
	}
	return p.ID
}

func addFilesFromTemplateProject(templateProjectID, projectID, owner string) {
	projectDirID := getProjectDirID(projectID)
	rql := r.Table("project2datafile").GetAllByIndex("project_id", templateProjectID)
	var projectFiles []schema.Project2DataFile
	if err := model.ProjectFiles.Qs(session).Rows(rql, &projectFiles); err != nil {
		fmt.Printf("Unable to get files for template project %s: %s\n", templateProjectID, err)
		os.Exit(1)
	}

	for _, p2f := range projectFiles {
		f, err := rfiles.ByID(p2f.DataFileID)
		if err != nil {
			fmt.Printf("Unable to retrieve file %s for project %s: %s\n", p2f.DataFileID, projectID, err)
		}
		f.UsesID = f.ID
		f.ID = ""
		f.Owner = owner
		if _, err := rfiles.Insert(f, projectDirID, projectID); err != nil {
			fmt.Printf("Failed to insert file %s into project %s: %s\n", f.Name, projectID, err)
		}
	}
}

func getProjectDirID(projectID string) string {
	var projectDir schema.Project2DataDir
	rql := r.Table("project2datadir").GetAllByIndex("project_id", projectID)
	if err := model.GetRow(rql, session, &projectDir); err != nil {
		fmt.Printf("Unable to get project directory for %s: %s\n", projectID, err)
		os.Exit(1)
	}

	return projectDir.DataDirID
}
<|endoftext|>


<commit_before>// Package main extracts attachments from the provided email
package main

import (
	"flag"
	"fmt"
	"github.com/jhillyerd/enmime"
	"github.com/jhillyerd/enmime/cmd"
	"io"
	"os"
	"path"
	"strings"
)

var (
	mimefile = flag.String("f", "", "mime(eml) file")
	outdir   = flag.String("o", "", "output dir")
)

func main() {
	flag.Parse()
	if *mimefile == "" {
		fmt.Fprintln(os.Stderr, "Missing filename argument")
		flag.Usage()
		os.Exit(1)
	}
	cwd, _ := os.Getwd()
	if *outdir == "" {
		outdir = &cwd
	}
	fmt.Fprintf(os.Stdout, "Extract attachments in %s\n", *outdir)

	if err := os.MkdirAll(*outdir, os.ModePerm); err != nil {
		fmt.Fprintf(os.Stderr, "Mkdir %s failed.", *outdir)
		os.Exit(2)
	}

	reader, err := os.Open(*mimefile)
	if err != nil {
		fmt.Fprintln(os.Stderr, "Failed to open file:", err)
		os.Exit(1)
	}

	basename := path.Base(*mimefile)
	if err = dump(reader, basename); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func dump(reader io.Reader, name string) error {
	// Parse message body with enmime
	e, err := enmime.ReadEnvelope(reader)
	if err != nil {
		return fmt.Errorf("During enmime.ReadEnvelope: %v", err)
	}

	h1(name)

	h2("Envelope")
	fmt.Printf("From: %v  \n", e.GetHeader("From"))
	fmt.Printf("To: %v  \n", e.GetHeader("To"))
	fmt.Printf("Subject: %v  \n", e.GetHeader("Subject"))
	fmt.Println()

	h2("Body Text")
	fmt.Println(e.Text)
	fmt.Println()

	h2("Body HTML")
	fmt.Println(e.HTML)
	fmt.Println()

	h2("Attachment List")
	for _, a := range e.Attachments {
		newFileName := path.Join(*outdir, a.FileName)
		f, err := os.Create(newFileName)
		if err != nil {
			fmt.Printf("Error creating file %q: %v\n", newFileName, err)
		}
		_, err = io.Copy(f, a)
		if err != nil {
			fmt.Printf("Error writing file %q: %v\n", newFileName, err)
		}
		err = f.Close()
		if err != nil {
			fmt.Printf("Error closing file %q: %v\n", newFileName, err)
		}
		fmt.Printf("- %v (%v)\n", a.FileName, a.ContentType)
	}
	fmt.Println()

	h2("MIME Part Tree")
	if e.Root == nil {
		fmt.Println("Message was not MIME encoded")
	} else {
		cmd.FormatPart(os.Stdout, e.Root, "    ")
	}

	return nil
}

func h1(content string) {
	bar := strings.Repeat("=", len(content))
	fmt.Printf("%v\n%v\n\n", content, bar)
}

func h2(content string) {
	bar := strings.Repeat("-", len(content))
	fmt.Printf("%v\n%v\n", content, bar)
}
<commit_msg>Refactor mime-extractor to use cmd utils<commit_after>// Package main extracts attachments from the provided email
package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"path"

	"github.com/jhillyerd/enmime"
	"github.com/jhillyerd/enmime/cmd"
)

var (
	mimefile = flag.String("f", "", "mime(eml) file")
	outdir   = flag.String("o", "", "output dir")
)

func main() {
	flag.Parse()
	if *mimefile == "" {
		fmt.Fprintln(os.Stderr, "Missing filename argument")
		flag.Usage()
		os.Exit(1)
	}
	cwd, _ := os.Getwd()
	if *outdir == "" {
		outdir = &cwd
	}

	if err := os.MkdirAll(*outdir, os.ModePerm); err != nil {
		fmt.Fprintf(os.Stderr, "Mkdir %s failed.", *outdir)
		os.Exit(2)
	}

	reader, err := os.Open(*mimefile)
	if err != nil {
		fmt.Fprintln(os.Stderr, "Failed to open file:", err)
		os.Exit(1)
	}

	// basename is used as the markdown title
	basename := path.Base(*mimefile)
	e, err := enmime.ReadEnvelope(reader)
	if err != nil {
		fmt.Fprintln(os.Stderr, "During enmime.ReadEnvelope:", err)
		os.Exit(1)
	}

	if err = cmd.EnvelopeToMarkdown(os.Stdout, e, basename); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	// Write out attachments
	fmt.Fprintf(os.Stderr, "\nExtracting attachments into %s...", *outdir)
	for _, a := range e.Attachments {
		newFileName := path.Join(*outdir, a.FileName)
		f, err := os.Create(newFileName)
		if err != nil {
			fmt.Printf("Error creating file %q: %v\n", newFileName, err)
			break
		}
		_, err = io.Copy(f, a)
		if err != nil {
			fmt.Printf("Error writing file %q: %v\n", newFileName, err)
			break
		}
		if f.Close() != nil {
			fmt.Printf("Error closing file %q: %v\n", newFileName, err)
			break
		}
	}
	fmt.Fprintln(os.Stderr, " Done!")
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
)

type FactorioServer struct {
	Cmd      *exec.Cmd
	Savefile string
	Latency  int  `json:"latency"`
	Port     int  `json:"port"`
	Running  bool `json:"running"`
	StdOut   io.ReadCloser
	StdErr   io.ReadCloser
	StdIn    io.WriteCloser
	Settings FactorioServerSettings
}

type FactorioServerSettings struct {
	Name                                 string   `json:"name"`
	Description                          string   `json:"description"`
	Tags                                 []string `json:"tags"`
	MaxPlayers                           int      `json:"max_players"`
	Visibility                           string   `json:"visibility"`
	Username                             string   `json:"username"`
	Password                             string   `json:"password"`
	Token                                string   `json:"token"`
	GamePassword                         string   `json:"game_password"`
	RequireUserVerification              bool     `json:"require_user_verification"`
	MaxUploadInKilobytesPerSecond        int      `json:"max_upload_in_kilobytes_per_second"`
	IgnorePlayerLimitForReturningPlayers bool     `json:"ignore_player_limit_for_returning_players"`
	AllowCommands                        string   `json:"allow_commands"`
	AutosaveInterval                     int      `json:"autosave_interval"`
	AutosaveSlots                        int      `json:"autosave_slots"`
	AfkAutoKickInterval                  int      `json:"afk_autokick_interval"`
	AutoPause                            bool     `json:"auto_pause"`
	OnlyAdminsCanPauseThegame            bool     `json:"only_admins_can_pause_the_game"`
	Admins                               []string `json:"admins"`
	AutosaveOnlyOnServer                 bool     `json:"autosave_only_on_server"`
}

func initFactorio() *FactorioServer {
	f := FactorioServer{}
	settingsFile := filepath.Join(config.FactorioConfigDir, config.SettingsFile)

	if _, err := os.Stat(settingsFile); err == nil {
		// server-settings.json file exists
		settings, err := os.Open(settingsFile)
		if err != nil {
			log.Printf("Error in reading %s: %s", settingsFile, err)
		}
		defer settings.Close()

		settingsParser := json.NewDecoder(settings)
		if err = settingsParser.Decode(&f.Settings); err != nil {
			log.Printf("Error in reading %s: %s", settingsFile, err)
		}
		log.Printf("Loaded Factorio settings from %s, settings: %+v", settingsFile, &f.Settings)

	} else {
		// default settings from server-settings.example.json
		f.Settings = FactorioServerSettings{
			Name:                                 "Factorio",
			Description:                          "Created by Factorio Server Manager",
			Tags:                                 []string{},
			MaxPlayers:                           0,
			Visibility:                           "public",
			Username:                             "",
			Password:                             "",
			Token:                                "",
			GamePassword:                         "",
			RequireUserVerification:              true,
			MaxUploadInKilobytesPerSecond:        0,
			IgnorePlayerLimitForReturningPlayers: false,
			AllowCommands:                        "admins-only",
			AutosaveInterval:                     10,
			AutosaveSlots:                        5,
			AfkAutoKickInterval:                  0,
			AutosaveOnlyOnServer:                 true,
			AutoPause:                            true,
			OnlyAdminsCanPauseThegame:            true,
			Admins: []string{},
		}
		log.Printf("Loaded Default Factorio settings settings: %+v", &f.Settings)
	}

	return &f
}

func (f *FactorioServer) Run() error {
	var err error

	data, err := json.MarshalIndent(f.Settings, "", "  ")
	if err != nil {
		log.Println("Failed to marshal FactorioServerSettings: ", err)
	} else {
		ioutil.WriteFile(filepath.Join(config.FactorioDir, "server-settings.json"), data, 0644)
	}

	args := []string{
		"--start-server", filepath.Join(config.FactorioSavesDir, f.Savefile),
		"--port", strconv.Itoa(f.Port),
		"--server-settings", filepath.Join(config.FactorioDir, "server-settings.json"),
	}

	log.Println("Starting server with command: ", config.FactorioBinary, args)

	f.Cmd = exec.Command(config.FactorioBinary, args...)

	f.StdOut, err = f.Cmd.StdoutPipe()
	if err != nil {
		log.Printf("Error opening stdout pipe: %s", err)
		return err
	}

	f.StdIn, err = f.Cmd.StdinPipe()
	if err != nil {
		log.Printf("Error opening stdin pipe: %s", err)
		return err
	}

	f.StdErr, err = f.Cmd.StderrPipe()
	if err != nil {
		log.Printf("Error opening stderr pipe: %s", err)
		return err
	}

	go io.Copy(os.Stdout, f.StdOut)
	go io.Copy(os.Stderr, f.StdErr)

	err = f.Cmd.Start()
	if err != nil {
		log.Printf("Error starting server process: %s", err)
		return err
	}

	f.Running = true

	err = f.Cmd.Wait()
	if err != nil {
		log.Printf("Command exited with error: %s", err)
		return err
	}

	return nil
}

func (f *FactorioServer) Stop() error {
	// TODO: Find an alternative to os.Kill on Windows. os.Interupt
	// is not implemented. Maps will not be saved.
	if runtime.GOOS == "windows" {
		err := f.Cmd.Process.Signal(os.Kill)
		if err != nil {
			log.Printf("Error sending SIGKILLL to Factorio process: %s", err)
			return err
		} else {
			f.Running = false
			log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.")
		}
	} else {
		err := f.Cmd.Process.Signal(os.Interrupt)
		if err != nil {
			log.Printf("Error sending SIGINT to Factorio process: %s", err)
			return err
		} else {
			f.Running = false
			log.Printf("Sent SIGINT to Factorio process. Factorio shutting down...")
		}
	}

	return nil
}
<commit_msg>Starting server saves server-settings.json file to correct position<commit_after>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
)

type FactorioServer struct {
	Cmd      *exec.Cmd
	Savefile string
	Latency  int  `json:"latency"`
	Port     int  `json:"port"`
	Running  bool `json:"running"`
	StdOut   io.ReadCloser
	StdErr   io.ReadCloser
	StdIn    io.WriteCloser
	Settings FactorioServerSettings
}

type FactorioServerSettings struct {
	Name                                 string   `json:"name"`
	Description                          string   `json:"description"`
	Tags                                 []string `json:"tags"`
	MaxPlayers                           int      `json:"max_players"`
	Visibility                           string   `json:"visibility"`
	Username                             string   `json:"username"`
	Password                             string   `json:"password"`
	Token                                string   `json:"token"`
	GamePassword                         string   `json:"game_password"`
	RequireUserVerification              bool     `json:"require_user_verification"`
	MaxUploadInKilobytesPerSecond        int      `json:"max_upload_in_kilobytes_per_second"`
	IgnorePlayerLimitForReturningPlayers bool     `json:"ignore_player_limit_for_returning_players"`
	AllowCommands                        string   `json:"allow_commands"`
	AutosaveInterval                     int      `json:"autosave_interval"`
	AutosaveSlots                        int      `json:"autosave_slots"`
	AfkAutoKickInterval                  int      `json:"afk_autokick_interval"`
	AutoPause                            bool     `json:"auto_pause"`
	OnlyAdminsCanPauseThegame            bool     `json:"only_admins_can_pause_the_game"`
	Admins                               []string `json:"admins"`
	AutosaveOnlyOnServer                 bool     `json:"autosave_only_on_server"`
}

func initFactorio() *FactorioServer {
	f := FactorioServer{}
	settingsFile := filepath.Join(config.FactorioConfigDir, config.SettingsFile)

	if _, err := os.Stat(settingsFile); err == nil {
		// server-settings.json file exists
		settings, err := os.Open(settingsFile)
		if err != nil {
			log.Printf("Error in reading %s: %s", settingsFile, err)
		}
		defer settings.Close()

		settingsParser := json.NewDecoder(settings)
		if err = settingsParser.Decode(&f.Settings); err != nil {
			log.Printf("Error in reading %s: %s", settingsFile, err)
		}
		log.Printf("Loaded Factorio settings from %s, settings: %+v", settingsFile, &f.Settings)

	} else {
		// default settings from server-settings.example.json
		f.Settings = FactorioServerSettings{
			Name:                                 "Factorio",
			Description:                          "Created by Factorio Server Manager",
			Tags:                                 []string{},
			MaxPlayers:                           0,
			Visibility:                           "public",
			Username:                             "",
			Password:                             "",
			Token:                                "",
			GamePassword:                         "",
			RequireUserVerification:              true,
			MaxUploadInKilobytesPerSecond:        0,
			IgnorePlayerLimitForReturningPlayers: false,
			AllowCommands:                        "admins-only",
			AutosaveInterval:                     10,
			AutosaveSlots:                        5,
			AfkAutoKickInterval:                  0,
			AutosaveOnlyOnServer:                 true,
			AutoPause:                            true,
			OnlyAdminsCanPauseThegame:            true,
			Admins: []string{},
		}
		log.Printf("Loaded Default Factorio settings settings: %+v", &f.Settings)
	}

	return &f
}

func (f *FactorioServer) Run() error {
	var err error

	data, err := json.MarshalIndent(f.Settings, "", "  ")
	if err != nil {
		log.Println("Failed to marshal FactorioServerSettings: ", err)
	} else {
		ioutil.WriteFile(filepath.Join(config.FactorioConfigDir, config.SettingsFile), data, 0644)
	}

	args := []string{
		"--start-server", filepath.Join(config.FactorioSavesDir, f.Savefile),
		"--port", strconv.Itoa(f.Port),
		"--server-settings", filepath.Join(config.FactorioDir, "server-settings.json"),
	}

	log.Println("Starting server with command: ", config.FactorioBinary, args)

	f.Cmd = exec.Command(config.FactorioBinary, args...)

	f.StdOut, err = f.Cmd.StdoutPipe()
	if err != nil {
		log.Printf("Error opening stdout pipe: %s", err)
		return err
	}

	f.StdIn, err = f.Cmd.StdinPipe()
	if err != nil {
		log.Printf("Error opening stdin pipe: %s", err)
		return err
	}

	f.StdErr, err = f.Cmd.StderrPipe()
	if err != nil {
		log.Printf("Error opening stderr pipe: %s", err)
		return err
	}

	go io.Copy(os.Stdout, f.StdOut)
	go io.Copy(os.Stderr, f.StdErr)

	err = f.Cmd.Start()
	if err != nil {
		log.Printf("Error starting server process: %s", err)
		return err
	}

	f.Running = true

	err = f.Cmd.Wait()
	if err != nil {
		log.Printf("Command exited with error: %s", err)
		return err
	}

	return nil
}

func (f *FactorioServer) Stop() error {
	// TODO: Find an alternative to os.Kill on Windows. os.Interupt
	// is not implemented. Maps will not be saved.
	if runtime.GOOS == "windows" {
		err := f.Cmd.Process.Signal(os.Kill)
		if err != nil {
			log.Printf("Error sending SIGKILLL to Factorio process: %s", err)
			return err
		} else {
			f.Running = false
			log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.")
		}
	} else {
		err := f.Cmd.Process.Signal(os.Interrupt)
		if err != nil {
			log.Printf("Error sending SIGINT to Factorio process: %s", err)
			return err
		} else {
			f.Running = false
			log.Printf("Sent SIGINT to Factorio process. Factorio shutting down...")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package catalog

import (
	"bytes"
	"fmt"
	"net/http"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/relistan/go-director"
)

const (
	CLIENT_TIMEOUT = 3 * time.Second
	DEFAULT_RETRIES = 5
)

type UrlListener struct {
	Url          string
	Retries      int
	Client       *http.Client
	looper       director.Looper
	eventChannel chan ChangeEvent
}

func NewUrlListener(url string) *UrlListener {
	errorChan := make(chan error, 1)

	return &UrlListener{
		Url:          url,
		looper:       director.NewFreeLooper(director.FOREVER, errorChan),
		Client:       &http.Client{Timeout: CLIENT_TIMEOUT},
		eventChannel: make(chan ChangeEvent, 20),
		Retries:      DEFAULT_RETRIES,
	}
}

func withRetries(count int, fn func() error) error {
	var result error

	for i := -1; i < count; i++ {
		result = fn()
		if result == nil {
			return nil
		}
		time.Sleep(100*time.Duration(i)*time.Millisecond)
	}

	log.Warnf("Failed after %d retries", count)
	return result
}

func (u *UrlListener) Watch(state *ServicesState) {
	state.AddListener(u.eventChannel)

	go func() {
		// We don't care what the change was, we post them all, so
		// just listen for any event.
		u.looper.Loop(func() error {
			<-u.eventChannel
			data := state.Encode()

			// Check for some kind of junk JSON being generated by state.Encode()
			if len(data) < 10 {
				log.Warnf("Skipping post to '%s' because of bad state encoding!", u.Url)
				return nil
			}

			buf := bytes.NewBuffer(data)

			err := withRetries(u.Retries, func() error {
				resp, err := u.Client.Post(u.Url, "application/json", buf)

				if err != nil {
					return err
				}

				if resp.StatusCode > 299 || resp.StatusCode < 200 {
					return fmt.Errorf("Bad status code returned (%d)", resp.StatusCode)
				}

				return nil
			})

			if err != nil {
				log.Warnf("Failed posting state to '%s': %s", u.Url, err.Error())
			}

			return nil
		})
	}()
}
<commit_msg>Pass full event + state over HTTP.<commit_after>package catalog

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/relistan/go-director"
)

const (
	CLIENT_TIMEOUT  = 3 * time.Second
	DEFAULT_RETRIES = 5
)

type UrlListener struct {
	Url          string
	Retries      int
	Client       *http.Client
	looper       director.Looper
	eventChannel chan ChangeEvent
}

type StateChangedEvent struct {
	State       ServicesState
	ChangeEvent ChangeEvent
}

func NewUrlListener(url string) *UrlListener {
	errorChan := make(chan error, 1)

	return &UrlListener{
		Url:          url,
		looper:       director.NewFreeLooper(director.FOREVER, errorChan),
		Client:       &http.Client{Timeout: CLIENT_TIMEOUT},
		eventChannel: make(chan ChangeEvent, 20),
		Retries:      DEFAULT_RETRIES,
	}
}

func withRetries(count int, fn func() error) error {
	var result error

	for i := -1; i < count; i++ {
		result = fn()
		if result == nil {
			return nil
		}
		time.Sleep(100 * time.Duration(i) * time.Millisecond)
	}

	log.Warnf("Failed after %d retries", count)
	return result
}

func (u *UrlListener) Watch(state *ServicesState) {
	state.AddListener(u.eventChannel)

	go func() {
		u.looper.Loop(func() error {
			changedServiceEvent := <-u.eventChannel

			state.Lock()
			event := StateChangedEvent{
				State:       *state,
				ChangeEvent: changedServiceEvent,
			}

			data, err := json.Marshal(event)
			state.Unlock()

			// Check for some kind of junk JSON being generated by state.Encode()
			if err != nil {
				log.Warnf("Skipping post to '%s' because of bad state encoding! (%s)", u.Url, err.Error())
				return nil
			}

			buf := bytes.NewBuffer(data)

			err = withRetries(u.Retries, func() error {
				resp, err := u.Client.Post(u.Url, "application/json", buf)

				if err != nil {
					return err
				}

				if resp.StatusCode > 299 || resp.StatusCode < 200 {
					return fmt.Errorf("Bad status code returned (%d)", resp.StatusCode)
				}

				return nil
			})

			if err != nil {
				log.Warnf("Failed posting state to '%s': %s", u.Url, err.Error())
			}

			return nil
		})
	}()
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/godbus/dbus"
	"github.com/muka/go-bluetooth/bluez"
	"github.com/muka/go-bluetooth/bluez/profile"
	"github.com/muka/go-bluetooth/emitter"
	"github.com/muka/go-bluetooth/util"
)

var deviceRegistry = make(map[string]*Device)

// NewDevice creates a new Device
func NewDevice(path string) *Device {

	if _, ok := deviceRegistry[path]; ok {
		return deviceRegistry[path]
	}

	d := new(Device)
	d.Path = path
	d.client = profile.NewDevice1(path)

	d.client.GetProperties()
	d.Properties = d.client.Properties
	d.chars = make(map[dbus.ObjectPath]*profile.GattCharacteristic1, 0)

	deviceRegistry[path] = d

	// d.watchProperties()

	return d
}

//ClearDevice free a device struct
func ClearDevice(d *Device) error {

	err := d.Disconnect()
	if err != nil {
		return err
	}

	err = d.unwatchProperties()
	if err != nil {
		return err
	}

	c, err := d.GetClient()
	if err != nil {
		return err
	}

	c.Close()

	if _, ok := deviceRegistry[d.Path]; ok {
		delete(deviceRegistry, d.Path)
	}

	return nil
}

//ClearDevices clear cached devices
func ClearDevices() error {
	devices, err := GetDevices()
	if err != nil {
		panic(err)
	}
	for _, dev := range devices {
		err := ClearDevice(&dev)
		if err != nil {
			return err
		}
	}
	return nil
}

// ParseDevice parse a Device from a ObjectManager map
func ParseDevice(path dbus.ObjectPath, propsMap map[string]dbus.Variant) (*Device, error) {

	d := new(Device)
	d.Path = string(path)
	d.client = profile.NewDevice1(d.Path)

	props := new(profile.Device1Properties)
	util.MapToStruct(props, propsMap)
	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}
	c.Properties = props
	d.chars = make(map[dbus.ObjectPath]*profile.GattCharacteristic1, 0)

	return d, nil
}

func (d *Device) watchProperties() error {

	channel, err := d.client.Register()
	if err != nil {
		return err
	}

	go (func() {
		for {

			if channel == nil {
				break
			}

			sig := <-channel

			if sig == nil {
				return
			}

			if sig.Name != bluez.PropertiesChanged {
				continue
			}

			// for i := 0; i < len(sig.Body); i++ {
			// log.Printf("%s -> %s\n", reflect.TypeOf(sig.Body[i]), sig.Body[i])
			// }

			iface := sig.Body[0].(string)
			changes := sig.Body[1].(map[string]dbus.Variant)
			for field, val := range changes {

				// updates [*]Properties struct
				props := d.Properties

				s := reflect.ValueOf(props).Elem()
				// exported field
				f := s.FieldByName(field)
				if f.IsValid() {
					// A Value can be changed only if it is
					// addressable and was not obtained by
					// the use of unexported struct fields.
					if f.CanSet() {
						x := reflect.ValueOf(val.Value())
						f.Set(x)
					}
				}

				propChanged := PropertyChangedEvent{string(iface), field, val.Value(), props, d}
				d.Emit("changed", propChanged)
			}
		}
	})()

	return nil
}

//Device return an API to interact with a DBus device
type Device struct {
	Path       string
	Properties *profile.Device1Properties
	client     *profile.Device1
	chars      map[dbus.ObjectPath]*profile.GattCharacteristic1
}

func (d *Device) unwatchProperties() error {
	return d.client.Unregister()
}

//GetClient return a DBus Device1 interface client
func (d *Device) GetClient() (*profile.Device1, error) {
	if d.client == nil {
		return nil, errors.New("Client not available")
	}
	return d.client, nil
}

//GetProperties return the properties for the device
func (d *Device) GetProperties() (*profile.Device1Properties, error) {

	if d == nil {
		return nil, errors.New("Empty device pointer")
	}

	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}

	props, err := c.GetProperties()

	if err != nil {
		return nil, err
	}

	d.Properties = props
	return d.Properties, err
}

//GetProperty return a property value
func (d *Device) GetProperty(name string) (data interface{}, err error) {
	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}
	val, err := c.GetProperty(name)
	if err != nil {
		return nil, err
	}
	return val.Value(), nil
}

//On register callback for event
func (d *Device) On(name string, fn *emitter.Callback) {
	switch name {
	case "changed":
		d.watchProperties()
		break
	}
	emitter.On(d.Path+"."+name, fn)
}

//Off unregister callback for event
func (d *Device) Off(name string, cb *emitter.Callback) {
	switch name {
	case "changed":
		d.unwatchProperties()
		break
	}

	pattern := d.Path + "." + name
	if name != "*" {
		emitter.Off(pattern, cb)
	} else {
		emitter.RemoveListeners(pattern, nil)
	}
}

//Emit an event
func (d *Device) Emit(name string, data interface{}) {
	emitter.Emit(d.Path+"."+name, data)
}

//GetService return a GattService
func (d *Device) GetService(path string) *profile.GattService1 {
	return profile.NewGattService1(path, "org.bluez")
}

//GetChar return a GattService
func (d *Device) GetChar(path string) *profile.GattCharacteristic1 {
	return profile.NewGattCharacteristic1(path)
}

//GetAllServicesAndUUID return a list of uuid's with their corresponding services
func (d *Device) GetAllServicesAndUUID() ([]string, error) {

	list := d.GetCharsList()

	var deviceFound []string
	var uuidAndService string
	for _, path := range list {

		_, ok := d.chars[path]
		if !ok {
			d.chars[path] = profile.NewGattCharacteristic1(string(path))
		}

		props := d.chars[path].Properties
		cuuid := strings.ToUpper(props.UUID)
		service := string(props.Service)

		uuidAndService = fmt.Sprint(cuuid, ":", service)
		deviceFound = append(deviceFound, uuidAndService)
	}

	return deviceFound, nil
}

//GetCharByUUID return a GattService by its uuid, return nil if not found
func (d *Device) GetCharByUUID(uuid string) (*profile.GattCharacteristic1, error) {

	uuid = strings.ToUpper(uuid)

	list := d.GetCharsList()

	var deviceFound *profile.GattCharacteristic1

	for _, path := range list {

		// use cache
		_, ok := d.chars[path]
		if !ok {
			d.chars[path] = profile.NewGattCharacteristic1(string(path))
		}

		props := d.chars[path].Properties
		cuuid := strings.ToUpper(props.UUID)

		if cuuid == uuid {
			deviceFound = d.chars[path]
		}
	}

	// if deviceFound == nil {
	// 	dbgDevice("Characteristic not Found: %s ", uuid)
	// }

	return deviceFound, nil
}

//GetCharsList return a device characteristics
func (d *Device) GetCharsList() []dbus.ObjectPath {

	var chars []dbus.ObjectPath

	if len(d.chars) != 0 {

		for objpath := range d.chars {
			chars = append(chars, objpath)
		}

		return chars
	}

	list := GetManager().GetObjects()
	for objpath := range *list {
		path := string(objpath)
		if !strings.HasPrefix(path, d.Path) {
			continue
		}
		charPos := strings.Index(path, "char")
		if charPos == -1 {
			continue
		}
		if strings.Index(path[charPos:], "desc") != -1 {
			continue
		}

		chars = append(chars, objpath)
	}

	return chars
}

//IsConnected check if connected to the device
func (d *Device) IsConnected() bool {

	props, _ := d.GetProperties()

	if props == nil {
		return false
	}

	return props.Connected
}

//Connect to device
func (d *Device) Connect() error {

	c, err := d.GetClient()
	if err != nil {
		return err
	}

	err = c.Connect()
	if err != nil {
		return err
	}
	return nil
}

//Disconnect from a device
func (d *Device) Disconnect() error {
	c, err := d.GetClient()
	if err != nil {
		return err
	}
	c.Disconnect()
	return nil
}

//Pair a device
func (d *Device) Pair() error {
	c, err := d.GetClient()
	if err != nil {
		return err
	}
	c.Pair()
	return nil
}
<commit_msg>Return a nil object and error if deviceFound is nil.<commit_after>package api

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/godbus/dbus"
	"github.com/muka/go-bluetooth/bluez"
	"github.com/muka/go-bluetooth/bluez/profile"
	"github.com/muka/go-bluetooth/emitter"
	"github.com/muka/go-bluetooth/util"
)

var deviceRegistry = make(map[string]*Device)

// NewDevice creates a new Device
func NewDevice(path string) *Device {

	if _, ok := deviceRegistry[path]; ok {
		return deviceRegistry[path]
	}

	d := new(Device)
	d.Path = path
	d.client = profile.NewDevice1(path)

	d.client.GetProperties()
	d.Properties = d.client.Properties
	d.chars = make(map[dbus.ObjectPath]*profile.GattCharacteristic1, 0)

	deviceRegistry[path] = d

	// d.watchProperties()

	return d
}

//ClearDevice free a device struct
func ClearDevice(d *Device) error {

	err := d.Disconnect()
	if err != nil {
		return err
	}

	err = d.unwatchProperties()
	if err != nil {
		return err
	}

	c, err := d.GetClient()
	if err != nil {
		return err
	}

	c.Close()

	if _, ok := deviceRegistry[d.Path]; ok {
		delete(deviceRegistry, d.Path)
	}

	return nil
}

//ClearDevices clear cached devices
func ClearDevices() error {
	devices, err := GetDevices()
	if err != nil {
		panic(err)
	}
	for _, dev := range devices {
		err := ClearDevice(&dev)
		if err != nil {
			return err
		}
	}
	return nil
}

// ParseDevice parse a Device from a ObjectManager map
func ParseDevice(path dbus.ObjectPath, propsMap map[string]dbus.Variant) (*Device, error) {

	d := new(Device)
	d.Path = string(path)
	d.client = profile.NewDevice1(d.Path)

	props := new(profile.Device1Properties)
	util.MapToStruct(props, propsMap)
	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}
	c.Properties = props
	d.chars = make(map[dbus.ObjectPath]*profile.GattCharacteristic1, 0)

	return d, nil
}

func (d *Device) watchProperties() error {

	channel, err := d.client.Register()
	if err != nil {
		return err
	}

	go (func() {
		for {

			if channel == nil {
				break
			}

			sig := <-channel

			if sig == nil {
				return
			}

			if sig.Name != bluez.PropertiesChanged {
				continue
			}

			// for i := 0; i < len(sig.Body); i++ {
			// log.Printf("%s -> %s\n", reflect.TypeOf(sig.Body[i]), sig.Body[i])
			// }

			iface := sig.Body[0].(string)
			changes := sig.Body[1].(map[string]dbus.Variant)
			for field, val := range changes {

				// updates [*]Properties struct
				props := d.Properties

				s := reflect.ValueOf(props).Elem()
				// exported field
				f := s.FieldByName(field)
				if f.IsValid() {
					// A Value can be changed only if it is
					// addressable and was not obtained by
					// the use of unexported struct fields.
					if f.CanSet() {
						x := reflect.ValueOf(val.Value())
						f.Set(x)
					}
				}

				propChanged := PropertyChangedEvent{string(iface), field, val.Value(), props, d}
				d.Emit("changed", propChanged)
			}
		}
	})()

	return nil
}

//Device return an API to interact with a DBus device
type Device struct {
	Path       string
	Properties *profile.Device1Properties
	client     *profile.Device1
	chars      map[dbus.ObjectPath]*profile.GattCharacteristic1
}

func (d *Device) unwatchProperties() error {
	return d.client.Unregister()
}

//GetClient return a DBus Device1 interface client
func (d *Device) GetClient() (*profile.Device1, error) {
	if d.client == nil {
		return nil, errors.New("Client not available")
	}
	return d.client, nil
}

//GetProperties return the properties for the device
func (d *Device) GetProperties() (*profile.Device1Properties, error) {

	if d == nil {
		return nil, errors.New("Empty device pointer")
	}

	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}

	props, err := c.GetProperties()

	if err != nil {
		return nil, err
	}

	d.Properties = props
	return d.Properties, err
}

//GetProperty return a property value
func (d *Device) GetProperty(name string) (data interface{}, err error) {
	c, err := d.GetClient()
	if err != nil {
		return nil, err
	}
	val, err := c.GetProperty(name)
	if err != nil {
		return nil, err
	}
	return val.Value(), nil
}

//On register callback for event
func (d *Device) On(name string, fn *emitter.Callback) {
	switch name {
	case "changed":
		d.watchProperties()
		break
	}
	emitter.On(d.Path+"."+name, fn)
}

//Off unregister callback for event
func (d *Device) Off(name string, cb *emitter.Callback) {
	switch name {
	case "changed":
		d.unwatchProperties()
		break
	}

	pattern := d.Path + "." + name
	if name != "*" {
		emitter.Off(pattern, cb)
	} else {
		emitter.RemoveListeners(pattern, nil)
	}
}

//Emit an event
func (d *Device) Emit(name string, data interface{}) {
	emitter.Emit(d.Path+"."+name, data)
}

//GetService return a GattService
func (d *Device) GetService(path string) *profile.GattService1 {
	return profile.NewGattService1(path, "org.bluez")
}

//GetChar return a GattService
func (d *Device) GetChar(path string) *profile.GattCharacteristic1 {
	return profile.NewGattCharacteristic1(path)
}

//GetAllServicesAndUUID return a list of uuid's with their corresponding services
func (d *Device) GetAllServicesAndUUID() ([]string, error) {

	list := d.GetCharsList()

	var deviceFound []string
	var uuidAndService string
	for _, path := range list {

		_, ok := d.chars[path]
		if !ok {
			d.chars[path] = profile.NewGattCharacteristic1(string(path))
		}

		props := d.chars[path].Properties
		cuuid := strings.ToUpper(props.UUID)
		service := string(props.Service)

		uuidAndService = fmt.Sprint(cuuid, ":", service)
		deviceFound = append(deviceFound, uuidAndService)
	}

	return deviceFound, nil
}

//GetCharByUUID return a GattService by its uuid, return nil if not found
func (d *Device) GetCharByUUID(uuid string) (*profile.GattCharacteristic1, error) {

	uuid = strings.ToUpper(uuid)

	list := d.GetCharsList()

	var deviceFound *profile.GattCharacteristic1

	for _, path := range list {

		// use cache
		_, ok := d.chars[path]
		if !ok {
			d.chars[path] = profile.NewGattCharacteristic1(string(path))
		}

		props := d.chars[path].Properties
		cuuid := strings.ToUpper(props.UUID)

		if cuuid == uuid {
			deviceFound = d.chars[path]
		}
	}

	if deviceFound == nil {
    return nil, errors.New("Characteristic not found.")
	}

	return deviceFound, nil
}

//GetCharsList return a device characteristics
func (d *Device) GetCharsList() []dbus.ObjectPath {

	var chars []dbus.ObjectPath

	if len(d.chars) != 0 {

		for objpath := range d.chars {
			chars = append(chars, objpath)
		}

		return chars
	}

	list := GetManager().GetObjects()
	for objpath := range *list {
		path := string(objpath)
		if !strings.HasPrefix(path, d.Path) {
			continue
		}
		charPos := strings.Index(path, "char")
		if charPos == -1 {
			continue
		}
		if strings.Index(path[charPos:], "desc") != -1 {
			continue
		}

		chars = append(chars, objpath)
	}

	return chars
}

//IsConnected check if connected to the device
func (d *Device) IsConnected() bool {

	props, _ := d.GetProperties()

	if props == nil {
		return false
	}

	return props.Connected
}

//Connect to device
func (d *Device) Connect() error {

	c, err := d.GetClient()
	if err != nil {
		return err
	}

	err = c.Connect()
	if err != nil {
		return err
	}
	return nil
}

//Disconnect from a device
func (d *Device) Disconnect() error {
	c, err := d.GetClient()
	if err != nil {
		return err
	}
	c.Disconnect()
	return nil
}

//Pair a device
func (d *Device) Pair() error {
	c, err := d.GetClient()
	if err != nil {
		return err
	}
	c.Pair()
	return nil
}
<|endoftext|>


<commit_before>// screen
package main

import (
	"errors"
	"fmt"
	termbox "github.com/nsf/termbox-go"
)

func drawfield(lField <-chan Field, rField <-chan Field) error {
	var lFl, rFl Field
	fmt.Println("We are in")
	//Straight up copy from docs about how initialize our little image
	if err := termbox.Init(); err != nil {
		panic(err)
	}
	defer termbox.Close()

	fmt.Println("Initialized")
	termSizW, termSizH := termbox.Size()
	fmt.Println(termSizH, termSizW)
	//We are getting field parameters. Possibly need to read them from rules, but...
	lFl = <-lField
	rFl = <-rField
	//Sanity checks
	if (lFl.Fheight != rFl.Fheight) && (lFl.Fwidth != rFl.Fwidth) {
		return errors.New("Can't do mismatched fields yet")
	}

	sFlen := lFl.Fheight + rFl.Fheight + 1
	sFwid := lFl.Fwidth + rFl.Fwidth + 3
	//one field in Heigth, two fields in Width, plus line for identifications of rows and columns

	//And more sanity checks
	if (termSizW < sFwid) && (termSizH < sFlen) {
		return errors.New("Terminal too small")
	}

	for varW := (termSizW - sFwid); varW < termSizW; varW++ {
		for varH := (termSizH - sFlen); varH < termSizH; varH++ {
			termbox.SetCell(varW, varH, ' ', termbox.ColorWhite, termbox.ColorBlack)

		}
	}

	return nil //Yay!
}
<commit_msg>and finished typesafing<commit_after>// screen
package main

import (
	"errors"
	"fmt"
	termbox "github.com/nsf/termbox-go"
)

func drawfield(lField <-chan Field, rField <-chan Field) error {
	var lFl, rFl Field
	fmt.Println("We are in")
	//Straight up copy from docs about how initialize our little image
	if err := termbox.Init(); err != nil {
		panic(err)
	}
	defer termbox.Close()

	fmt.Println("Initialized")
	termSizW, termSizH := termbox.Size()
	fmt.Println(termSizH, termSizW)
	//We are getting field parameters. Possibly need to read them from rules, but...
	lFl = <-lField
	rFl = <-rField

	//Sanity checks
	if (lFl.Fheight != rFl.Fheight) && (lFl.Fwidth != rFl.Fwidth) {
		return errors.New("Can't do mismatched fields yet")
	}

	sFhei := int(lFl.Fheight + rFl.Fheight + 1)
	sFwid := int(lFl.Fwidth + rFl.Fwidth + 3)
	//one field in Heigth, two fields in Width, plus line for identifications of rows and columns

	//And more sanity checks
	if (termSizW < sFwid) && (termSizH < sFhei) {
		return errors.New("Terminal too small")
	}

	for varW := (termSizW - sFwid); varW < termSizW; varW++ {
		for varH := (termSizH - sFhei); varH < termSizH; varH++ {
			termbox.SetCell(varW, varH, ' ', termbox.ColorWhite, termbox.ColorBlack)

		}
	}

	return nil //Yay!
}
<|endoftext|>