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

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os"
	"runtime"

	"github.com/vishvananda/netlink"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	current "github.com/containernetworking/cni/pkg/types/100"
	"github.com/containernetworking/cni/pkg/version"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ipam"
	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/containernetworking/plugins/pkg/utils"
	bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
	"github.com/containernetworking/plugins/pkg/utils/sysctl"
)

func init() {
	// this ensures that main runs only on main thread (thread group leader).
	// since namespace ops (unshare, setns) are done for a single thread, we
	// must ensure that the goroutine does not jump from OS thread to thread
	runtime.LockOSThread()
}

type NetConf struct {
	types.NetConf
	IPMasq bool `json:"ipMasq"`
	MTU    int  `json:"mtu"`
}

func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
	// The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1.
	// What we want is really a point-to-point link but veth does not support IFF_POINTTOPOINT.
	// Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and
	// add a route like "192.168.3.0/24 via 192.168.3.1 dev $ifName".
	// Unfortunately that won't work as the GW will be outside the interface's subnet.

	// Our solution is to configure the interface with 192.168.3.5/24, then delete the
	// "192.168.3.0/24 dev $ifName" route that was automatically added. Then we add
	// "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName".
	// In other words we force all traffic to ARP via the gateway except for GW itself.

	hostInterface := &current.Interface{}
	containerInterface := &current.Interface{}

	err := netns.Do(func(hostNS ns.NetNS) error {
		hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, "", hostNS)
		if err != nil {
			return err
		}
		hostInterface.Name = hostVeth.Name
		hostInterface.Mac = hostVeth.HardwareAddr.String()
		containerInterface.Name = contVeth0.Name
		containerInterface.Mac = contVeth0.HardwareAddr.String()
		containerInterface.Sandbox = netns.Path()

		for _, ipc := range pr.IPs {
			// All addresses apply to the container veth interface
			ipc.Interface = current.Int(1)
		}

		pr.Interfaces = []*current.Interface{hostInterface, containerInterface}

		contVeth, err := net.InterfaceByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to look up %q: %v", ifName, err)
		}

		_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", ifName), "1")

		if err = ipam.ConfigureIface(ifName, pr); err != nil {
			return err
		}

		for _, ipc := range pr.IPs {
			// Delete the route that was automatically added
			route := netlink.Route{
				LinkIndex: contVeth.Index,
				Dst: &net.IPNet{
					IP:   ipc.Address.IP.Mask(ipc.Address.Mask),
					Mask: ipc.Address.Mask,
				},
				Scope: netlink.SCOPE_NOWHERE,
			}

			if err := netlink.RouteDel(&route); err != nil {
				return fmt.Errorf("failed to delete route %v: %v", route, err)
			}

			addrBits := 32
			if ipc.Address.IP.To4() == nil {
				addrBits = 128
			}

			for _, r := range []netlink.Route{
				{
					LinkIndex: contVeth.Index,
					Dst: &net.IPNet{
						IP:   ipc.Gateway,
						Mask: net.CIDRMask(addrBits, addrBits),
					},
					Scope: netlink.SCOPE_LINK,
					Src:   ipc.Address.IP,
				},
				{
					LinkIndex: contVeth.Index,
					Dst: &net.IPNet{
						IP:   ipc.Address.IP.Mask(ipc.Address.Mask),
						Mask: ipc.Address.Mask,
					},
					Scope: netlink.SCOPE_UNIVERSE,
					Gw:    ipc.Gateway,
					Src:   ipc.Address.IP,
				},
			} {
				if err := netlink.RouteAdd(&r); err != nil {
					return fmt.Errorf("failed to add route %v: %v", r, err)
				}
			}
		}

		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	return hostInterface, containerInterface, nil
}

func setupHostVeth(vethName string, result *current.Result) error {
	// hostVeth moved namespaces and may have a new ifindex
	veth, err := netlink.LinkByName(vethName)
	if err != nil {
		return fmt.Errorf("failed to lookup %q: %v", vethName, err)
	}

	for _, ipc := range result.IPs {
		maskLen := 128
		if ipc.Address.IP.To4() != nil {
			maskLen = 32
		}

		ipn := &net.IPNet{
			IP:   ipc.Gateway,
			Mask: net.CIDRMask(maskLen, maskLen),
		}
		addr := &netlink.Addr{IPNet: ipn, Label: ""}
		if err = netlink.AddrAdd(veth, addr); err != nil {
			return fmt.Errorf("failed to add IP addr (%#v) to veth: %v", ipn, err)
		}

		ipn = &net.IPNet{
			IP:   ipc.Address.IP,
			Mask: net.CIDRMask(maskLen, maskLen),
		}
		// dst happens to be the same as IP/net of host veth
		if err = ip.AddHostRoute(ipn, nil, veth); err != nil && !os.IsExist(err) {
			return fmt.Errorf("failed to add route on host: %v", err)
		}
	}

	return nil
}

func cmdAdd(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	// run the IPAM plugin and get back the config to apply
	r, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
	if err != nil {
		return err
	}

	// Invoke ipam del if err to avoid ip leak
	defer func() {
		if err != nil {
			ipam.ExecDel(conf.IPAM.Type, args.StdinData)
		}
	}()

	// Convert whatever the IPAM result was into the current Result type
	result, err := current.NewResultFromResult(r)
	if err != nil {
		return err
	}

	if len(result.IPs) == 0 {
		return errors.New("IPAM plugin returned missing IP config")
	}

	if err := ip.EnableForward(result.IPs); err != nil {
		return fmt.Errorf("Could not enable IP forwarding: %v", err)
	}

	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	hostInterface, _, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
	if err != nil {
		return err
	}

	if err = setupHostVeth(hostInterface.Name, result); err != nil {
		return err
	}

	if conf.IPMasq {
		chain := utils.FormatChainName(conf.Name, args.ContainerID)
		comment := utils.FormatComment(conf.Name, args.ContainerID)
		for _, ipc := range result.IPs {
			if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
				return err
			}
		}
	}

	// Only override the DNS settings in the previous result if any DNS fields
	// were provided to the ptp plugin. This allows, for example, IPAM plugins
	// to specify the DNS settings instead of the ptp plugin.
	if dnsConfSet(conf.DNS) {
		result.DNS = conf.DNS
	}

	return types.PrintResult(result, conf.CNIVersion)
}

func dnsConfSet(dnsConf types.DNS) bool {
	return dnsConf.Nameservers != nil ||
		dnsConf.Search != nil ||
		dnsConf.Options != nil ||
		dnsConf.Domain != ""
}

func cmdDel(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if err := ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil {
		return err
	}

	if args.Netns == "" {
		return nil
	}

	// There is a netns so try to clean up. Delete can be called multiple times
	// so don't return an error if the device is already removed.
	// If the device isn't there then don't try to clean up IP masq either.
	var ipnets []*net.IPNet
	err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
		var err error
		ipnets, err = ip.DelLinkByNameAddr(args.IfName)
		if err != nil && err == ip.ErrLinkNotFound {
			return nil
		}
		return err
	})

	if err != nil {
		return err
	}

	if len(ipnets) != 0 && conf.IPMasq {
		chain := utils.FormatChainName(conf.Name, args.ContainerID)
		comment := utils.FormatComment(conf.Name, args.ContainerID)
		for _, ipn := range ipnets {
			err = ip.TeardownIPMasq(ipn, chain, comment)
		}
	}

	return err
}

func main() {
	skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
}

func cmdCheck(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	// run the IPAM plugin and get back the config to apply
	err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
	if err != nil {
		return err
	}
	if conf.NetConf.RawPrevResult == nil {
		return fmt.Errorf("ptp: Required prevResult missing")
	}
	if err := version.ParsePrevResult(&conf.NetConf); err != nil {
		return err
	}
	// Convert whatever the IPAM result was into the current Result type
	result, err := current.NewResultFromResult(conf.PrevResult)
	if err != nil {
		return err
	}

	var contMap current.Interface
	// Find interfaces for name whe know, that of host-device inside container
	for _, intf := range result.Interfaces {
		if args.IfName == intf.Name {
			if args.Netns == intf.Sandbox {
				contMap = *intf
				continue
			}
		}
	}

	// The namespace must be the same as what was configured
	if args.Netns != contMap.Sandbox {
		return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
			contMap.Sandbox, args.Netns)
	}

	//
	// Check prevResults for ips, routes and dns against values found in the container
	if err := netns.Do(func(_ ns.NetNS) error {

		// Check interface against values found in the container
		err := validateCniContainerInterface(contMap)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedRoute(result.Routes)
		if err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

func validateCniContainerInterface(intf current.Interface) error {

	var link netlink.Link
	var err error

	if intf.Name == "" {
		return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
	}
	link, err = netlink.LinkByName(intf.Name)
	if err != nil {
		return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
	}
	if intf.Sandbox == "" {
		return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
	}

	_, isVeth := link.(*netlink.Veth)
	if !isVeth {
		return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name)
	}

	if intf.Mac != "" {
		if intf.Mac != link.Attrs().HardwareAddr.String() {
			return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
		}
	}

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

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os"
	"runtime"

	"github.com/vishvananda/netlink"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	current "github.com/containernetworking/cni/pkg/types/100"
	"github.com/containernetworking/cni/pkg/version"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ipam"
	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/containernetworking/plugins/pkg/utils"
	bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)

func init() {
	// this ensures that main runs only on main thread (thread group leader).
	// since namespace ops (unshare, setns) are done for a single thread, we
	// must ensure that the goroutine does not jump from OS thread to thread
	runtime.LockOSThread()
}

type NetConf struct {
	types.NetConf
	IPMasq bool `json:"ipMasq"`
	MTU    int  `json:"mtu"`
}

func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
	// The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1.
	// What we want is really a point-to-point link but veth does not support IFF_POINTTOPOINT.
	// Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and
	// add a route like "192.168.3.0/24 via 192.168.3.1 dev $ifName".
	// Unfortunately that won't work as the GW will be outside the interface's subnet.

	// Our solution is to configure the interface with 192.168.3.5/24, then delete the
	// "192.168.3.0/24 dev $ifName" route that was automatically added. Then we add
	// "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName".
	// In other words we force all traffic to ARP via the gateway except for GW itself.

	hostInterface := &current.Interface{}
	containerInterface := &current.Interface{}

	err := netns.Do(func(hostNS ns.NetNS) error {
		hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, "", hostNS)
		if err != nil {
			return err
		}
		hostInterface.Name = hostVeth.Name
		hostInterface.Mac = hostVeth.HardwareAddr.String()
		containerInterface.Name = contVeth0.Name
		containerInterface.Mac = contVeth0.HardwareAddr.String()
		containerInterface.Sandbox = netns.Path()

		for _, ipc := range pr.IPs {
			// All addresses apply to the container veth interface
			ipc.Interface = current.Int(1)
		}

		pr.Interfaces = []*current.Interface{hostInterface, containerInterface}

		contVeth, err := net.InterfaceByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to look up %q: %v", ifName, err)
		}

		if err = ipam.ConfigureIface(ifName, pr); err != nil {
			return err
		}

		for _, ipc := range pr.IPs {
			// Delete the route that was automatically added
			route := netlink.Route{
				LinkIndex: contVeth.Index,
				Dst: &net.IPNet{
					IP:   ipc.Address.IP.Mask(ipc.Address.Mask),
					Mask: ipc.Address.Mask,
				},
				Scope: netlink.SCOPE_NOWHERE,
			}

			if err := netlink.RouteDel(&route); err != nil {
				return fmt.Errorf("failed to delete route %v: %v", route, err)
			}

			addrBits := 32
			if ipc.Address.IP.To4() == nil {
				addrBits = 128
			}

			for _, r := range []netlink.Route{
				{
					LinkIndex: contVeth.Index,
					Dst: &net.IPNet{
						IP:   ipc.Gateway,
						Mask: net.CIDRMask(addrBits, addrBits),
					},
					Scope: netlink.SCOPE_LINK,
					Src:   ipc.Address.IP,
				},
				{
					LinkIndex: contVeth.Index,
					Dst: &net.IPNet{
						IP:   ipc.Address.IP.Mask(ipc.Address.Mask),
						Mask: ipc.Address.Mask,
					},
					Scope: netlink.SCOPE_UNIVERSE,
					Gw:    ipc.Gateway,
					Src:   ipc.Address.IP,
				},
			} {
				if err := netlink.RouteAdd(&r); err != nil {
					return fmt.Errorf("failed to add route %v: %v", r, err)
				}
			}
		}

		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	return hostInterface, containerInterface, nil
}

func setupHostVeth(vethName string, result *current.Result) error {
	// hostVeth moved namespaces and may have a new ifindex
	veth, err := netlink.LinkByName(vethName)
	if err != nil {
		return fmt.Errorf("failed to lookup %q: %v", vethName, err)
	}

	for _, ipc := range result.IPs {
		maskLen := 128
		if ipc.Address.IP.To4() != nil {
			maskLen = 32
		}

		ipn := &net.IPNet{
			IP:   ipc.Gateway,
			Mask: net.CIDRMask(maskLen, maskLen),
		}
		addr := &netlink.Addr{IPNet: ipn, Label: ""}
		if err = netlink.AddrAdd(veth, addr); err != nil {
			return fmt.Errorf("failed to add IP addr (%#v) to veth: %v", ipn, err)
		}

		ipn = &net.IPNet{
			IP:   ipc.Address.IP,
			Mask: net.CIDRMask(maskLen, maskLen),
		}
		// dst happens to be the same as IP/net of host veth
		if err = ip.AddHostRoute(ipn, nil, veth); err != nil && !os.IsExist(err) {
			return fmt.Errorf("failed to add route on host: %v", err)
		}
	}

	return nil
}

func cmdAdd(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	// run the IPAM plugin and get back the config to apply
	r, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
	if err != nil {
		return err
	}

	// Invoke ipam del if err to avoid ip leak
	defer func() {
		if err != nil {
			ipam.ExecDel(conf.IPAM.Type, args.StdinData)
		}
	}()

	// Convert whatever the IPAM result was into the current Result type
	result, err := current.NewResultFromResult(r)
	if err != nil {
		return err
	}

	if len(result.IPs) == 0 {
		return errors.New("IPAM plugin returned missing IP config")
	}

	if err := ip.EnableForward(result.IPs); err != nil {
		return fmt.Errorf("Could not enable IP forwarding: %v", err)
	}

	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	hostInterface, _, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
	if err != nil {
		return err
	}

	if err = setupHostVeth(hostInterface.Name, result); err != nil {
		return err
	}

	if conf.IPMasq {
		chain := utils.FormatChainName(conf.Name, args.ContainerID)
		comment := utils.FormatComment(conf.Name, args.ContainerID)
		for _, ipc := range result.IPs {
			if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
				return err
			}
		}
	}

	// Only override the DNS settings in the previous result if any DNS fields
	// were provided to the ptp plugin. This allows, for example, IPAM plugins
	// to specify the DNS settings instead of the ptp plugin.
	if dnsConfSet(conf.DNS) {
		result.DNS = conf.DNS
	}

	return types.PrintResult(result, conf.CNIVersion)
}

func dnsConfSet(dnsConf types.DNS) bool {
	return dnsConf.Nameservers != nil ||
		dnsConf.Search != nil ||
		dnsConf.Options != nil ||
		dnsConf.Domain != ""
}

func cmdDel(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	if err := ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil {
		return err
	}

	if args.Netns == "" {
		return nil
	}

	// There is a netns so try to clean up. Delete can be called multiple times
	// so don't return an error if the device is already removed.
	// If the device isn't there then don't try to clean up IP masq either.
	var ipnets []*net.IPNet
	err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
		var err error
		ipnets, err = ip.DelLinkByNameAddr(args.IfName)
		if err != nil && err == ip.ErrLinkNotFound {
			return nil
		}
		return err
	})

	if err != nil {
		return err
	}

	if len(ipnets) != 0 && conf.IPMasq {
		chain := utils.FormatChainName(conf.Name, args.ContainerID)
		comment := utils.FormatComment(conf.Name, args.ContainerID)
		for _, ipn := range ipnets {
			err = ip.TeardownIPMasq(ipn, chain, comment)
		}
	}

	return err
}

func main() {
	skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
}

func cmdCheck(args *skel.CmdArgs) error {
	conf := NetConf{}
	if err := json.Unmarshal(args.StdinData, &conf); err != nil {
		return fmt.Errorf("failed to load netconf: %v", err)
	}

	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	// run the IPAM plugin and get back the config to apply
	err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
	if err != nil {
		return err
	}
	if conf.NetConf.RawPrevResult == nil {
		return fmt.Errorf("ptp: Required prevResult missing")
	}
	if err := version.ParsePrevResult(&conf.NetConf); err != nil {
		return err
	}
	// Convert whatever the IPAM result was into the current Result type
	result, err := current.NewResultFromResult(conf.PrevResult)
	if err != nil {
		return err
	}

	var contMap current.Interface
	// Find interfaces for name whe know, that of host-device inside container
	for _, intf := range result.Interfaces {
		if args.IfName == intf.Name {
			if args.Netns == intf.Sandbox {
				contMap = *intf
				continue
			}
		}
	}

	// The namespace must be the same as what was configured
	if args.Netns != contMap.Sandbox {
		return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
			contMap.Sandbox, args.Netns)
	}

	//
	// Check prevResults for ips, routes and dns against values found in the container
	if err := netns.Do(func(_ ns.NetNS) error {

		// Check interface against values found in the container
		err := validateCniContainerInterface(contMap)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedRoute(result.Routes)
		if err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

func validateCniContainerInterface(intf current.Interface) error {

	var link netlink.Link
	var err error

	if intf.Name == "" {
		return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
	}
	link, err = netlink.LinkByName(intf.Name)
	if err != nil {
		return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
	}
	if intf.Sandbox == "" {
		return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
	}

	_, isVeth := link.(*netlink.Veth)
	if !isVeth {
		return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name)
	}

	if intf.Mac != "" {
		if intf.Mac != link.Attrs().HardwareAddr.String() {
			return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package prove

import (
	"log"
	"time"
	"unsafe"

	"github.com/nelhage/taktician/ai"
	"github.com/nelhage/taktician/bitboard"
	"github.com/nelhage/taktician/ptn"
	"github.com/nelhage/taktician/tak"
)

const poolSize = 1024

type positionPool struct {
	buf  [poolSize]*tak.Position
	r, w int
}

type DFPNStats struct {
	Work       uint64
	Repetition uint64

	Terminal uint64
	Solved   uint64
	Hits     uint64
	Miss     uint64
}

type DFPNSolver struct {
	attacker tak.Color
	table    dfpnTable
	debug    int

	stats DFPNStats

	stack   []dfpnFrame
	killers []tak.Move

	pool positionPool

	c bitboard.Constants
}

type dfpnFrame struct {
	g *tak.Position
	m tak.Move
}

type DFPNConfig struct {
	Debug    int
	Attacker tak.Color
	TableMem int64
}

type proofNumbers struct {
	phi, delta uint32
}

const INFINITY uint32 = 1 << 30

func (pn proofNumbers) exceeded(other proofNumbers) bool {
	return pn.phi >= other.phi || pn.delta >= other.delta
}

func (pn *proofNumbers) solved() bool {
	return pn.phi == 0 || pn.delta == 0
}

type dfpnTable struct {
	entries []entry
}

func (t *dfpnTable) lookup(g *tak.Position) (entry, bool) {
	e := t.entries[g.Hash()%uint64(len(t.entries))]
	if e.hash == g.Hash() {
		return e, true
	}
	return entry{}, false
}

func (t *dfpnTable) store(e *entry) bool {
	target := &t.entries[e.hash%uint64(len(t.entries))]
	if target.work <= e.work {
		*target = *e
		return true
	}

	return false
}

type entry struct {
	bounds proofNumbers
	hash   uint64
	work   uint64
	pv     tak.Move
	//	child  uint8
}

type dfpnChild struct {
	data entry
	move tak.Move
	g    *tak.Position
}

const defaultTableMem = 100 * 1024 * 1024

func NewDFPN(cfg *DFPNConfig) *DFPNSolver {
	if cfg.TableMem <= 0 {
		cfg.TableMem = defaultTableMem
	}
	return &DFPNSolver{
		attacker: cfg.Attacker,
		table: dfpnTable{
			entries: make([]entry, cfg.TableMem/int64(unsafe.Sizeof(entry{}))),
		},
		debug: cfg.Debug,
	}
}

func (d *DFPNSolver) Prove(g *tak.Position) (ProofResult, DFPNStats) {
	if d.attacker == tak.NoColor {
		d.attacker = g.ToMove()
	}
	d.c = bitboard.Precompute(uint(g.Size()))
	d.stats = DFPNStats{}

	d.stack = nil
	start := time.Now()
	entry, work := d.mid(g, proofNumbers{phi: INFINITY - 100, delta: INFINITY - 100}, entry{
		hash:   g.Hash(),
		work:   0,
		bounds: proofNumbers{phi: 1, delta: 1},
	})
	d.stats.Work = work
	duration := time.Since(start)
	var result Evaluation = EvalUnknown
	if entry.bounds.phi == 0 {
		result = EvalTrue
	} else if entry.bounds.delta == 0 {
		result = EvalFalse
	}
	return ProofResult{
		Result:   result,
		Move:     entry.pv,
		Proof:    entry.bounds.phi,
		Disproof: entry.bounds.delta,
		Duration: duration,
	}, d.stats
}

func (d *DFPNSolver) checkRepetition() bool {
	if len(d.stack) == 0 {
		return false
	}
	idx := len(d.stack) - 1
	cur := d.stack[idx].g.Hash()
	count := 0

	for idx > 0 {
		if d.stack[idx].g.Hash() == cur {
			count++
		}
		if count == 3 {
			return true
		}
		switch d.stack[idx].m.Type {
		case tak.PlaceFlat:
		case tak.PlaceStanding:
		case tak.PlaceCapstone:
			break
		}
		idx--
	}
	return false
}

func (d *DFPNSolver) alloc() *tak.Position {
	if d.pool.r == d.pool.w {
		return nil
	}
	p := d.pool.buf[d.pool.r]
	d.pool.r += 1
	if d.pool.r == len(d.pool.buf) {
		d.pool.r = 0
	}
	return p
}

func (d *DFPNSolver) release(p *tak.Position) {
	if (d.pool.w+1)%len(d.pool.buf) == d.pool.r {
		return
	}
	d.pool.buf[d.pool.w] = p
	d.pool.w = (d.pool.w + 1) % len(d.pool.buf)
}

func (d *DFPNSolver) solve(p *tak.Position) (bool, tak.Color) {
	wp, wt, bp, bt := ai.CountThreats(&d.c, p)
	if wp+wt > 0 && p.ToMove() == tak.White {
		return true, tak.White
	}
	if bp+bt > 0 && p.ToMove() == tak.Black {
		return true, tak.Black
	}
	return false, tak.NoColor
}

func (d *DFPNSolver) mid(g *tak.Position, bounds proofNumbers, current entry) (entry, uint64) {
	if current.bounds.exceeded(bounds) {
		return current, 0
	}

	/*
		if over, result := g.GameOver(); over {
			current.bounds = d.terminalBounds(g, result)
			return current, 0
		}
	*/

	if d.checkRepetition() {
		d.stats.Repetition++
		current.bounds = d.terminalBounds(g, tak.NoColor)
		return current, 0
	}

	if d.debug > 6 && len(d.stack) > 0 {
		log.Printf(" depth=%d toMove=%s move=%s current=(%d,%d) bounds=(%d,%d)",
			len(d.stack),
			g.ToMove(),
			ptn.FormatMove(d.stack[len(d.stack)-1].m),
			current.bounds.phi, current.bounds.delta,
			bounds.phi, bounds.delta)
	}

	localWork := uint64(1)
	// compute children
	var allocChildren [100]dfpnChild
	children := allocChildren[:0]
	var allocMoves [100]tak.Move
	moves := g.AllMoves(allocMoves[:0])
	defer func() {
		for _, ch := range children {
			d.release(ch.g)
		}
	}()

	depth := len(d.stack)
	var killer tak.Move
	if len(d.killers) > depth && d.killers[depth].Type != 0 {
		killer = d.killers[depth]
	}

	for _, m := range moves {
		alloc := d.alloc()
		p, err := g.MovePreallocated(m, alloc)
		if err != nil {
			d.release(alloc)
			continue
		}
		childEntry := entry{
			hash:   p.Hash(),
			bounds: proofNumbers{1, 1},
		}
		if over, result := p.GameOver(); over {
			d.stats.Terminal++
			childEntry.bounds = d.terminalBounds(p, result)
		} else if over, result := d.solve(p); over {
			d.stats.Solved++
			childEntry.bounds = d.terminalBounds(p, result)
		} else if b, ok := d.table.lookup(p); ok {
			d.stats.Hits++
			childEntry = b
		} else {
			d.stats.Miss++
			var buffer [100]tak.Move
			moves := len(p.AllMoves(buffer[:0]))
			childEntry.bounds = proofNumbers{phi: 1, delta: uint32(moves)}
		}
		children = append(children, dfpnChild{
			move: m,
			g:    p,
			data: childEntry,
		})
		if m == killer {
			children[0], children[len(children)-1] = children[len(children)-1], children[0]
		}

		if childEntry.bounds.delta == 0 {
			break
		}
	}

	for {
		current.bounds = computePNs(children)
		if current.bounds.exceeded(bounds) {
			break
		}

		best_idx, childBounds := d.selectChild(children, bounds, current.bounds)

		current.pv = children[best_idx].move

		d.stack = append(d.stack, dfpnFrame{children[best_idx].g, children[best_idx].move})
		newEntry, work := d.mid(children[best_idx].g, childBounds, children[best_idx].data)
		d.stack = d.stack[:len(d.stack)-1]

		children[best_idx].data = newEntry
		localWork += work
		current.work += work
	}

	if current.bounds.phi == 0 {
		for len(d.killers) <= depth {
			d.killers = append(d.killers, tak.Move{})
		}
		d.killers[depth] = current.pv
	}

	d.table.store(&current)

	return current, uint64(localWork)
}

func (d *DFPNSolver) terminalBounds(g *tak.Position, result tak.Color) proofNumbers {
	if result == tak.NoColor {
		result = d.attacker.Flip()
	}
	if result == g.ToMove() {
		return proofNumbers{phi: 0, delta: INFINITY}
	} else {
		return proofNumbers{phi: INFINITY, delta: 0}
	}
}

const epsilon = 0.1

func max(l, r uint32) uint32 {
	if l > r {
		return l
	}
	return r
}

func min(l, r uint32) uint32 {
	if l < r {
		return l
	}
	return r
}

func (d *DFPNSolver) selectChild(children []dfpnChild,
	bounds proofNumbers,
	pns proofNumbers) (int, proofNumbers) {
	delta1, delta2 := INFINITY, INFINITY

	best := -1
	for i, ch := range children {
		if ch.data.bounds.delta < delta1 {
			best = i
			delta2 = delta1
			delta1 = ch.data.bounds.delta
		} else if ch.data.bounds.delta < delta2 {
			delta2 = ch.data.bounds.delta
		}
	}

	phi1 := children[best].data.bounds.phi

	return best, proofNumbers{
		phi: bounds.delta + phi1 - pns.delta,
		delta: min(
			bounds.phi,
			max(delta2+1, uint32(float64(delta2)*(1.0+epsilon))),
			// delta2+1,
		),
	}
}

func computePNs(children []dfpnChild) proofNumbers {
	out := proofNumbers{phi: INFINITY, delta: 0}
	for _, ch := range children {
		out.delta += ch.data.bounds.phi
		if out.delta > INFINITY {
			out.delta = INFINITY
		}
		if ch.data.bounds.delta < out.phi {
			out.phi = ch.data.bounds.delta
		}
	}
	return out
}
<commit_msg>dfpn: some more funny math<commit_after>package prove

import (
	"log"
	"time"
	"unsafe"

	"github.com/nelhage/taktician/ai"
	"github.com/nelhage/taktician/bitboard"
	"github.com/nelhage/taktician/ptn"
	"github.com/nelhage/taktician/tak"
)

const poolSize = 1024

type positionPool struct {
	buf  [poolSize]*tak.Position
	r, w int
}

type DFPNStats struct {
	Work       uint64
	Repetition uint64

	Terminal uint64
	Solved   uint64
	Hits     uint64
	Miss     uint64
}

type DFPNSolver struct {
	attacker tak.Color
	table    dfpnTable
	debug    int

	stats DFPNStats

	stack   []dfpnFrame
	killers []tak.Move

	pool positionPool

	c bitboard.Constants
}

type dfpnFrame struct {
	g *tak.Position
	m tak.Move
}

type DFPNConfig struct {
	Debug    int
	Attacker tak.Color
	TableMem int64
}

type proofNumbers struct {
	phi, delta uint32
}

const INFINITY uint32 = 1 << 30

func (pn proofNumbers) exceeded(other proofNumbers) bool {
	return pn.phi >= other.phi || pn.delta >= other.delta
}

func (pn *proofNumbers) solved() bool {
	return pn.phi == 0 || pn.delta == 0
}

type dfpnTable struct {
	entries []entry
}

func (t *dfpnTable) lookup(g *tak.Position) (entry, bool) {
	e := t.entries[g.Hash()%uint64(len(t.entries))]
	if e.hash == g.Hash() {
		return e, true
	}
	return entry{}, false
}

func (t *dfpnTable) store(e *entry) bool {
	target := &t.entries[e.hash%uint64(len(t.entries))]
	if target.work <= e.work {
		*target = *e
		return true
	}

	return false
}

type entry struct {
	bounds proofNumbers
	hash   uint64
	work   uint64
	pv     tak.Move
	//	child  uint8
}

type dfpnChild struct {
	data entry
	move tak.Move
	g    *tak.Position
}

const defaultTableMem = 100 * 1024 * 1024

func NewDFPN(cfg *DFPNConfig) *DFPNSolver {
	if cfg.TableMem <= 0 {
		cfg.TableMem = defaultTableMem
	}
	return &DFPNSolver{
		attacker: cfg.Attacker,
		table: dfpnTable{
			entries: make([]entry, cfg.TableMem/int64(unsafe.Sizeof(entry{}))),
		},
		debug: cfg.Debug,
	}
}

func (d *DFPNSolver) Prove(g *tak.Position) (ProofResult, DFPNStats) {
	if d.attacker == tak.NoColor {
		d.attacker = g.ToMove()
	}
	d.c = bitboard.Precompute(uint(g.Size()))
	d.stats = DFPNStats{}

	d.stack = nil
	start := time.Now()
	entry, work := d.mid(g, proofNumbers{phi: INFINITY / 2, delta: INFINITY / 2}, entry{
		hash:   g.Hash(),
		work:   0,
		bounds: proofNumbers{phi: 1, delta: 1},
	})
	d.stats.Work = work
	duration := time.Since(start)
	var result Evaluation = EvalUnknown
	if entry.bounds.phi == 0 {
		result = EvalTrue
	} else if entry.bounds.delta == 0 {
		result = EvalFalse
	}
	return ProofResult{
		Result:   result,
		Move:     entry.pv,
		Proof:    entry.bounds.phi,
		Disproof: entry.bounds.delta,
		Duration: duration,
	}, d.stats
}

func (d *DFPNSolver) checkRepetition() bool {
	if len(d.stack) == 0 {
		return false
	}
	idx := len(d.stack) - 1
	cur := d.stack[idx].g.Hash()
	count := 0

	for idx > 0 {
		if d.stack[idx].g.Hash() == cur {
			count++
		}
		if count == 3 {
			return true
		}
		switch d.stack[idx].m.Type {
		case tak.PlaceFlat:
		case tak.PlaceStanding:
		case tak.PlaceCapstone:
			break
		}
		idx--
	}
	return false
}

func (d *DFPNSolver) alloc() *tak.Position {
	if d.pool.r == d.pool.w {
		return nil
	}
	p := d.pool.buf[d.pool.r]
	d.pool.r += 1
	if d.pool.r == len(d.pool.buf) {
		d.pool.r = 0
	}
	return p
}

func (d *DFPNSolver) release(p *tak.Position) {
	if (d.pool.w+1)%len(d.pool.buf) == d.pool.r {
		return
	}
	d.pool.buf[d.pool.w] = p
	d.pool.w = (d.pool.w + 1) % len(d.pool.buf)
}

func (d *DFPNSolver) solve(p *tak.Position) (bool, tak.Color) {
	wp, wt, bp, bt := ai.CountThreats(&d.c, p)
	if wp+wt > 0 && p.ToMove() == tak.White {
		return true, tak.White
	}
	if bp+bt > 0 && p.ToMove() == tak.Black {
		return true, tak.Black
	}
	return false, tak.NoColor
}

func (d *DFPNSolver) mid(g *tak.Position, bounds proofNumbers, current entry) (entry, uint64) {
	if current.bounds.exceeded(bounds) {
		return current, 0
	}

	/*
		if over, result := g.GameOver(); over {
			current.bounds = d.terminalBounds(g, result)
			return current, 0
		}
	*/

	if d.checkRepetition() {
		d.stats.Repetition++
		current.bounds = d.terminalBounds(g, tak.NoColor)
		return current, 0
	}

	if d.debug > 6 && len(d.stack) > 0 {
		log.Printf(" depth=%d toMove=%s move=%s current=(%d,%d) bounds=(%d,%d)",
			len(d.stack),
			g.ToMove(),
			ptn.FormatMove(d.stack[len(d.stack)-1].m),
			current.bounds.phi, current.bounds.delta,
			bounds.phi, bounds.delta)
	}

	localWork := uint64(1)
	// compute children
	var allocChildren [100]dfpnChild
	children := allocChildren[:0]
	var allocMoves [100]tak.Move
	moves := g.AllMoves(allocMoves[:0])
	defer func() {
		for _, ch := range children {
			d.release(ch.g)
		}
	}()

	depth := len(d.stack)
	var killer tak.Move
	if len(d.killers) > depth && d.killers[depth].Type != 0 {
		killer = d.killers[depth]
	}

	for _, m := range moves {
		alloc := d.alloc()
		p, err := g.MovePreallocated(m, alloc)
		if err != nil {
			d.release(alloc)
			continue
		}
		childEntry := entry{
			hash:   p.Hash(),
			bounds: proofNumbers{1, 1},
		}
		if over, result := p.GameOver(); over {
			d.stats.Terminal++
			childEntry.bounds = d.terminalBounds(p, result)
		} else if over, result := d.solve(p); over {
			d.stats.Solved++
			childEntry.bounds = d.terminalBounds(p, result)
		} else if b, ok := d.table.lookup(p); ok {
			d.stats.Hits++
			childEntry = b
		} else {
			d.stats.Miss++
			var buffer [100]tak.Move
			moves := len(p.AllMoves(buffer[:0]))
			childEntry.bounds = proofNumbers{phi: 1, delta: uint32(moves)}
		}
		children = append(children, dfpnChild{
			move: m,
			g:    p,
			data: childEntry,
		})
		if m == killer {
			children[0], children[len(children)-1] = children[len(children)-1], children[0]
		}

		if childEntry.bounds.delta == 0 {
			break
		}
	}

	for {
		current.bounds = computePNs(children)
		if current.bounds.exceeded(bounds) {
			break
		}

		best_idx, childBounds := d.selectChild(children, bounds, current.bounds)

		current.pv = children[best_idx].move

		d.stack = append(d.stack, dfpnFrame{children[best_idx].g, children[best_idx].move})
		newEntry, work := d.mid(children[best_idx].g, childBounds, children[best_idx].data)
		d.stack = d.stack[:len(d.stack)-1]

		children[best_idx].data = newEntry
		localWork += work
		current.work += work
	}

	if current.bounds.phi == 0 {
		for len(d.killers) <= depth {
			d.killers = append(d.killers, tak.Move{})
		}
		d.killers[depth] = current.pv
	}

	d.table.store(&current)

	return current, uint64(localWork)
}

func (d *DFPNSolver) terminalBounds(g *tak.Position, result tak.Color) proofNumbers {
	if result == tak.NoColor {
		result = d.attacker.Flip()
	}
	if result == g.ToMove() {
		return proofNumbers{phi: 0, delta: INFINITY}
	} else {
		return proofNumbers{phi: INFINITY, delta: 0}
	}
}

const epsilon = 0.1

func max(l, r uint32) uint32 {
	if l > r {
		return l
	}
	return r
}

func min(l, r uint32) uint32 {
	if l < r {
		return l
	}
	return r
}

func (d *DFPNSolver) selectChild(children []dfpnChild,
	bounds proofNumbers,
	pns proofNumbers) (int, proofNumbers) {
	delta1, delta2 := INFINITY, INFINITY

	best := -1
	for i, ch := range children {
		if ch.data.bounds.delta < delta1 {
			best = i
			delta2 = delta1
			delta1 = ch.data.bounds.delta
		} else if ch.data.bounds.delta < delta2 {
			delta2 = ch.data.bounds.delta
		}
	}

	phi1 := children[best].data.bounds.phi

	return best, proofNumbers{
		phi: bounds.delta + phi1 - pns.delta,
		delta: min(
			bounds.phi,
			min(
				max(delta2+1, uint32(float64(delta2)*(1.0+epsilon))),
				INFINITY,
			),
			// delta2+1,
		),
	}
}

func computePNs(children []dfpnChild) proofNumbers {
	out := proofNumbers{phi: INFINITY, delta: 0}
	for _, ch := range children {
		out.delta += ch.data.bounds.phi
		if out.delta > INFINITY {
			out.delta = INFINITY
		}
		if ch.data.bounds.delta < out.phi {
			out.phi = ch.data.bounds.delta
		}
	}

	return out
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	api "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

type nodeInfo struct {
	name string
	addr string
}

// return the IP addresses of all nodes in the cluster
func getKubePeers(c *kubernetes.Clientset) ([]nodeInfo, error) {
	nodeList, err := c.CoreV1().Nodes().List(api.ListOptions{})
	if err != nil {
		return nil, err
	}
	addresses := make([]nodeInfo, 0, len(nodeList.Items))
	for _, peer := range nodeList.Items {
		var internalIP, externalIP string
		for _, addr := range peer.Status.Addresses {
			// Check it's a valid ipv4 address
			ip := net.ParseIP(addr.Address)
			if ip == nil || ip.To4() == nil {
				continue
			}
			if addr.Type == "InternalIP" {
				internalIP = ip.To4().String()
			} else if addr.Type == "ExternalIP" {
				externalIP = ip.To4().String()
			}
		}

		// Fallback for cases where a Node has an ExternalIP but no InternalIP
		if internalIP != "" {
			addresses = append(addresses, nodeInfo{name: peer.Name, addr: internalIP})
		} else if externalIP != "" {
			addresses = append(addresses, nodeInfo{name: peer.Name, addr: externalIP})
		}
	}
	return addresses, nil
}

const (
	configMapName      = "weave-net"
	configMapNamespace = "kube-system"
)

// update the list of all peers that have gone through this code path
func addMyselfToPeerList(c *kubernetes.Clientset, peerName, name string) (*peerList, error) {
	cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
	var list *peerList
	err := cml.LoopUpdate(func() error {
		var err error
		list, err = cml.GetPeerList()
		if err != nil {
			return err
		}
		log.Println("Fetched existing peer list", list)
		if !list.contains(peerName) {
			list.add(peerName, name)
			log.Println("Storing new peer list", list)
			err = cml.UpdatePeerList(*list)
			if err != nil {
				return err
			}
		}
		return nil
	})
	return list, err
}

func main() {
	var (
		justReclaim bool
		peerName    string
		nodeName    string
	)
	flag.StringVar(&peerName, "peer-name", "unknown", "name of this Weave Net peer")
	flag.StringVar(&nodeName, "node-name", "unknown", "name of this Kubernetes node")
	flag.Parse()

	config, err := rest.InClusterConfig()
	if err != nil {
		log.Fatalf("Could not get cluster config: %v", err)
	}
	c, err := kubernetes.NewForConfig(config)
	if err != nil {
		log.Fatalf("Could not make Kubernetes connection: %v", err)
	}
	peers, err := getKubePeers(c)
	if err != nil {
		log.Fatalf("Could not get peers: %v", err)
	}
	if justReclaim {
		log.Println("Checking if any peers need to be reclaimed")
		_, err := addMyselfToPeerList(c, peerName, nodeName)
		if err != nil {
			log.Fatalf("Could not get peer list: %v", err)
		}
		return
	}
	for _, node := range peers {
		fmt.Println(node.addr)
	}
}
<commit_msg>Pseudo-code to clean up dead peers<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	api "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

type nodeInfo struct {
	name string
	addr string
}

// return the IP addresses of all nodes in the cluster
func getKubePeers(c *kubernetes.Clientset) ([]nodeInfo, error) {
	nodeList, err := c.CoreV1().Nodes().List(api.ListOptions{})
	if err != nil {
		return nil, err
	}
	addresses := make([]nodeInfo, 0, len(nodeList.Items))
	for _, peer := range nodeList.Items {
		var internalIP, externalIP string
		for _, addr := range peer.Status.Addresses {
			// Check it's a valid ipv4 address
			ip := net.ParseIP(addr.Address)
			if ip == nil || ip.To4() == nil {
				continue
			}
			if addr.Type == "InternalIP" {
				internalIP = ip.To4().String()
			} else if addr.Type == "ExternalIP" {
				externalIP = ip.To4().String()
			}
		}

		// Fallback for cases where a Node has an ExternalIP but no InternalIP
		if internalIP != "" {
			addresses = append(addresses, nodeInfo{name: peer.Name, addr: internalIP})
		} else if externalIP != "" {
			addresses = append(addresses, nodeInfo{name: peer.Name, addr: externalIP})
		}
	}
	return addresses, nil
}

const (
	configMapName      = "weave-net"
	configMapNamespace = "kube-system"
)

// update the list of all peers that have gone through this code path
func addMyselfToPeerList(c *kubernetes.Clientset, peerName, name string) (*peerList, error) {
	cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
	var list *peerList
	err := cml.LoopUpdate(func() error {
		var err error
		list, err = cml.GetPeerList()
		if err != nil {
			return err
		}
		log.Println("Fetched existing peer list", list)
		if !list.contains(peerName) {
			list.add(peerName, name)
			log.Println("Storing new peer list", list)
			err = cml.UpdatePeerList(*list)
			if err != nil {
				return err
			}
		}
		return nil
	})
	return list, err
}

// For each of those peers that is no longer listed as a node by
// Kubernetes, remove it from Weave IPAM
func reclaimRemovedPeers(apl *peerList, nodes []nodeInfo) error {
	// TODO
	// Outline of function:
	// 1. Compare peers stored in the peerList against all peers reported by k8s now.
	// 2. Loop for each X in the first set and not in the second - we wish to remove X from our data structures
	// 3. Check if there is an existing annotation with key X
	// 4.   If annotation already contains my identity, ok;
	// 5.   If non-existent, write an annotation with key X and contents "my identity"
	// 6.   If step 4 or 5 succeeded, rmpeer X
	// 7aa.   Remove any annotations Z* that have contents X
	// 7a.    Remove X from peerList
	// 7b.    Remove annotation with key X
	// 8.   If step 5 failed due to optimistic lock conflict, stop: someone else is handling X
	// 9. Go back to step 1 until there is no difference between the two sets

	// Step 3-5 is to protect against two simultaneous rmpeers of X
	// Step 4 is to pick up again after a restart between step 5 and step 7b
	// If the peer doing the reclaim disappears between steps 5 and 7a, then someone will clean it up in step 7aa
	// If peer doing the reclaim disappears forever between 7a and 7b then we get a dangling annotation
	// This should be sufficiently rare that we don't care.

	// Question: Should we narrow step 2 by checking against Weave Net IPAM?
	// i.e. If peer X owns any address space and is marked unreachable, we want to rmpeer X
	return nil
}

func main() {
	var (
		justReclaim bool
		peerName    string
		nodeName    string
	)
	flag.BoolVar(&justReclaim, "reclaim", false, "reclaim IP space from dead peers")
	flag.StringVar(&peerName, "peer-name", "unknown", "name of this Weave Net peer")
	flag.StringVar(&nodeName, "node-name", "unknown", "name of this Kubernetes node")
	flag.Parse()

	config, err := rest.InClusterConfig()
	if err != nil {
		log.Fatalf("Could not get cluster config: %v", err)
	}
	c, err := kubernetes.NewForConfig(config)
	if err != nil {
		log.Fatalf("Could not make Kubernetes connection: %v", err)
	}
	peers, err := getKubePeers(c)
	if err != nil {
		log.Fatalf("Could not get peers: %v", err)
	}
	if justReclaim {
		log.Println("Checking if any peers need to be reclaimed")
		_, err := addMyselfToPeerList(c, peerName, nodeName)
		if err != nil {
			log.Fatalf("Could not get peer list: %v", err)
		}
		err = reclaimRemovedPeers(list, peers)
		if err != nil {
			log.Fatalf("Error while reclaiming space: %v", err)
		}
		return
	}
	for _, node := range peers {
		fmt.Println(node.addr)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package alitasks

import (
	"fmt"

	"k8s.io/klog"

	"github.com/denverdino/aliyungo/common"
	"github.com/denverdino/aliyungo/ecs"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)

//go:generate fitask -type=VSwitchSNAT
type VSwitchSNAT struct {
	Name      *string
	Lifecycle *fi.Lifecycle
	ID        *string

	VSwitch     *VSwitch
	NatGateway  *NatGateway
	EIP         *EIP
	SnatTableId *string
	// Shared is set if this is a shared VSwitch
	Shared *bool
}

var _ fi.CompareWithID = &VSwitchSNAT{}

func (v *VSwitchSNAT) CompareWithID() *string {
	return v.Name
}

func (v *VSwitchSNAT) Find(c *fi.Context) (*VSwitchSNAT, error) {
	if v.VSwitch == nil || v.VSwitch.VSwitchId == nil {
		klog.V(4).Infof("VSwitch / VSwitchId not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if v.NatGateway == nil || v.NatGateway.ID == nil {
		klog.V(4).Infof("NatGateway / NatGatewayId not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if v.EIP == nil || v.EIP.IpAddress == nil {
		klog.V(4).Infof("EIP / EIP not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}

	cloud := c.Cloud.(aliup.ALICloud)

	describeNatGatewaysArgs := &ecs.DescribeNatGatewaysArgs{
		RegionId:     common.Region(cloud.Region()),
		NatGatewayId: fi.StringValue(v.NatGateway.ID),
	}

	natGateways, _, err := cloud.VpcClient().DescribeNatGateways(describeNatGatewaysArgs)
	if err != nil {
		return nil, fmt.Errorf("error listing NatGateways: %v", err)
	}
	if len(natGateways) == 0 {
		klog.V(4).Infof("NatGateway not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if len(natGateways[0].SnatTableIds.SnatTableId) == 0 {
		return nil, nil
	}

	for _, snatTableId := range natGateways[0].SnatTableIds.SnatTableId {

		describeSnatTableEntriesArgs := &ecs.DescribeSnatTableEntriesArgs{
			RegionId:    common.Region(cloud.Region()),
			SnatTableId: snatTableId,
		}
		snatTableEntries, _, err := cloud.VpcClient().DescribeSnatTableEntries(describeSnatTableEntriesArgs)
		if err != nil {
			return nil, fmt.Errorf("error listing snatTableEntries: %v", err)
		}
		if len(snatTableEntries) == 0 {
			continue
		}

		for _, snatEntry := range snatTableEntries {
			if snatEntry.SourceVSwitchId == fi.StringValue(v.VSwitch.VSwitchId) {
				actual := &VSwitchSNAT{}
				actual.ID = fi.String(snatEntry.SnatEntryId)
				v.ID = actual.ID
				actual.VSwitch = v.VSwitch
				actual.NatGateway = &NatGateway{ID: v.NatGateway.ID}
				actual.SnatTableId = fi.String(snatTableId)
				v.SnatTableId = actual.SnatTableId
				// Prevent spurious changes
				actual.Shared = v.Shared
				actual.Name = v.Name
				actual.Lifecycle = v.Lifecycle

				return actual, nil
			}
		}
	}
	v.SnatTableId = fi.String(natGateways[0].SnatTableIds.SnatTableId[0])
	return nil, nil
}

func (v *VSwitchSNAT) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(v, c)
}

func (v *VSwitchSNAT) CheckChanges(a, e, changes *VSwitchSNAT) error {
	if e.VSwitch == nil {
		return fi.RequiredField("VPC")
	}

	if e.NatGateway == nil {
		return fi.RequiredField("CIDRBlock")
	}

	if a != nil && changes != nil {
		if changes.VSwitch != nil {
			return fi.CannotChangeField("VSwitch")
		}

		if changes.NatGateway != nil {
			return fi.CannotChangeField("NatGateway")
		}
	}
	return nil
}

func (_ *VSwitchSNAT) RenderALI(t *aliup.ALIAPITarget, a, e, changes *VSwitchSNAT) error {

	if a == nil {
		createSnatEntryArgs := &ecs.CreateSnatEntryArgs{
			RegionId:        common.Region(t.Cloud.Region()),
			SnatTableId:     fi.StringValue(e.SnatTableId),
			SourceVSwitchId: fi.StringValue(e.VSwitch.VSwitchId),
			SnatIp:          fi.StringValue(e.EIP.IpAddress),
		}
		resp, err := t.Cloud.VpcClient().CreateSnatEntry(createSnatEntryArgs)
		if err != nil {
			return fmt.Errorf("error creating SnatEntry: %v,%v", err, createSnatEntryArgs)
		}
		e.ID = fi.String(resp.SnatEntryId)
	}
	return nil
}

type terraformVSwitchSNAT struct {
	SnatTableId *string            `json:"snat_table_id,omitempty"`
	VSwitchId   *terraform.Literal `json:"source_vswitch_id,omitempty"`
}

func (_ *VSwitchSNAT) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VSwitchSNAT) error {
	tf := &terraformVSwitchSNAT{
		SnatTableId: e.SnatTableId,
		VSwitchId:   e.VSwitch.TerraformLink(),
	}

	return t.RenderResource("alicloud_snat_entry", *e.Name, tf)
}
<commit_msg>Alicloud: fix comparison failures for vswitchSNAT<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package alitasks

import (
	"fmt"

	"k8s.io/klog"

	"github.com/denverdino/aliyungo/common"
	"github.com/denverdino/aliyungo/ecs"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)

//go:generate fitask -type=VSwitchSNAT
type VSwitchSNAT struct {
	Name      *string
	Lifecycle *fi.Lifecycle
	ID        *string

	VSwitch     *VSwitch
	NatGateway  *NatGateway
	EIP         *EIP
	SnatTableId *string
	// Shared is set if this is a shared VSwitch
	Shared *bool
}

var _ fi.CompareWithID = &VSwitchSNAT{}

func (v *VSwitchSNAT) CompareWithID() *string {
	return v.Name
}

func (v *VSwitchSNAT) Find(c *fi.Context) (*VSwitchSNAT, error) {
	if v.VSwitch == nil || v.VSwitch.VSwitchId == nil {
		klog.V(4).Infof("VSwitch / VSwitchId not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if v.NatGateway == nil || v.NatGateway.ID == nil {
		klog.V(4).Infof("NatGateway / NatGatewayId not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if v.EIP == nil || v.EIP.Name == nil {
		klog.V(4).Infof("EIP / EIPName not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}

	cloud := c.Cloud.(aliup.ALICloud)

	describeNatGatewaysArgs := &ecs.DescribeNatGatewaysArgs{
		RegionId:     common.Region(cloud.Region()),
		NatGatewayId: fi.StringValue(v.NatGateway.ID),
	}

	natGateways, _, err := cloud.VpcClient().DescribeNatGateways(describeNatGatewaysArgs)
	if err != nil {
		return nil, fmt.Errorf("error listing NatGateways: %v", err)
	}
	if len(natGateways) == 0 {
		klog.V(4).Infof("NatGateway not found for %s, skipping Find", fi.StringValue(v.Name))
		return nil, nil
	}
	if len(natGateways[0].SnatTableIds.SnatTableId) == 0 {
		return nil, nil
	}

	for _, snatTableId := range natGateways[0].SnatTableIds.SnatTableId {

		describeSnatTableEntriesArgs := &ecs.DescribeSnatTableEntriesArgs{
			RegionId:    common.Region(cloud.Region()),
			SnatTableId: snatTableId,
		}
		snatTableEntries, _, err := cloud.VpcClient().DescribeSnatTableEntries(describeSnatTableEntriesArgs)
		if err != nil {
			return nil, fmt.Errorf("error listing snatTableEntries: %v", err)
		}
		if len(snatTableEntries) == 0 {
			continue
		}

		for _, snatEntry := range snatTableEntries {
			if snatEntry.SourceVSwitchId == fi.StringValue(v.VSwitch.VSwitchId) {
				actual := &VSwitchSNAT{}
				actual.ID = fi.String(snatEntry.SnatEntryId)
				v.ID = actual.ID
				actual.VSwitch = v.VSwitch
				actual.NatGateway = &NatGateway{ID: v.NatGateway.ID}
				actual.SnatTableId = fi.String(snatTableId)
				v.SnatTableId = actual.SnatTableId
				// Prevent spurious changes
				actual.Shared = v.Shared
				actual.Name = v.Name
				actual.Lifecycle = v.Lifecycle

				describeEIPArgs := &ecs.DescribeEipAddressesArgs{
					RegionId:   common.Region(cloud.Region()),
					EipAddress: snatEntry.SnatIp,
				}

				eips, _, err := cloud.VpcClient().DescribeEipAddresses(describeEIPArgs)
				if err != nil {
					return nil, fmt.Errorf("error listing EIP: %v", err)
				}

				if len(eips) == 0 {
					klog.V(4).Infof("EIP not found for %s, skipping Find", snatEntry.SnatIp)
					return nil, nil
				}

				eip := eips[0]

				actual.EIP = &EIP{
					ID:        &eip.AllocationId,
					IpAddress: &eip.IpAddress,
				}

				return actual, nil
			}
		}
	}
	v.SnatTableId = fi.String(natGateways[0].SnatTableIds.SnatTableId[0])
	return nil, nil
}

func (v *VSwitchSNAT) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(v, c)
}

func (v *VSwitchSNAT) CheckChanges(a, e, changes *VSwitchSNAT) error {
	if e.VSwitch == nil {
		return fi.RequiredField("VPC")
	}

	if e.NatGateway == nil {
		return fi.RequiredField("CIDRBlock")
	}

	if a != nil && changes != nil {
		if changes.VSwitch != nil {
			return fi.CannotChangeField("VSwitch")
		}

		if changes.NatGateway != nil {
			return fi.CannotChangeField("NatGateway")
		}
	}
	return nil
}

func (_ *VSwitchSNAT) RenderALI(t *aliup.ALIAPITarget, a, e, changes *VSwitchSNAT) error {

	if a == nil {
		createSnatEntryArgs := &ecs.CreateSnatEntryArgs{
			RegionId:        common.Region(t.Cloud.Region()),
			SnatTableId:     fi.StringValue(e.SnatTableId),
			SourceVSwitchId: fi.StringValue(e.VSwitch.VSwitchId),
			SnatIp:          fi.StringValue(e.EIP.IpAddress),
		}
		resp, err := t.Cloud.VpcClient().CreateSnatEntry(createSnatEntryArgs)
		if err != nil {
			return fmt.Errorf("error creating SnatEntry: %v,%v", err, createSnatEntryArgs)
		}
		e.ID = fi.String(resp.SnatEntryId)
	}
	return nil
}

type terraformVSwitchSNAT struct {
	SnatTableId *string            `json:"snat_table_id,omitempty"`
	VSwitchId   *terraform.Literal `json:"source_vswitch_id,omitempty"`
}

func (_ *VSwitchSNAT) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VSwitchSNAT) error {
	tf := &terraformVSwitchSNAT{
		SnatTableId: e.SnatTableId,
		VSwitchId:   e.VSwitch.TerraformLink(),
	}

	return t.RenderResource("alicloud_snat_entry", *e.Name, tf)
}
<|endoftext|>


<commit_before>package models

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"strings"

	sh "github.com/codeskyblue/go-sh"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	git "gopkg.in/src-d/go-git.v4"
)

// Application : stores information about the applications
type Application struct {
	Name       string `json:"name"`
	Running    bool   `json:"running"`
	Path       string `json:"path"`
	Repository string `json:"repo"`
	Type       string `json:"type"`
}

// CreateApplication : Creates a new Application
func CreateApplication(w *os.File, name string) (Application, error) {
	home := getHomeFolder()
	executable := getExecutablePath()
	basePath := filepath.Join(home, "PiaaS-Data")
	path := filepath.Join(basePath, "Applications", name, "repo")

	if _, err := os.Stat(path); err == nil {
		printErr(w, "App already exists.")
		return Application{}, err
	}
	printNormal(w, "Creating Application '"+name+"'.")
	printNormal(w, "Creating directories")
	err := os.MkdirAll(path, os.ModePerm)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Creating directories.")

	printNormal(w, "Initializing git repository.")
	if _, err := git.PlainInit(path, true); err != nil {
		printErr(w, err)
		return Application{}, err
	}
	err = os.MkdirAll(filepath.Join(path, "hooks"), os.ModePerm)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	file, err := os.Create(filepath.Join(path, "hooks", "post-receive"))
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	defer file.Close()
	fmt.Fprintf(file, "#!/usr/bin/env bash\n%s/PiaaS app deploy %s\n", executable, name)
	err = os.Chmod(filepath.Join(path, "hooks", "post-receive"), 0755)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Initializing git repository.")
	printInfo(w, "Repository path: "+path)

	app := Application{}
	app.Name = name
	app.Path = filepath.Join(basePath, "Applications", name)
	app.Repository = path
	printNormal(w, "Creating database record.")
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Creating database record.")
	printSuccess(w, ("Application '" + name + "' successfully created."))
	return app, nil
}

// DeleteApplication : Deletes existing Application
func DeleteApplication(w *os.File, name string) (bool, error) {
	home := getHomeFolder()
	session := sh.NewSession()
	basePath := filepath.Join(home, "PiaaS-Data")
	path := filepath.Join(basePath, "Applications", name)
	if _, err := os.Stat(path); err != nil {
		printErr(w, "App does not exist.")
		return false, err
	}
	printNormal(w, ("Removing Application '" + name + "'."))
	printNormal(w, "Deleting Containers.")
	_, err := session.Command("docker", "rm", "--force", name).Output()
	if err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting Images.")
	printNormal(w, "Deleting Images.")
	_, err = session.Command("docker", "rmi", name).Output()
	if err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting Images.")
	printNormal(w, "Removing Directories")
	if err := os.RemoveAll(path); err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Removing directories.")
	printNormal(w, "Deleting Database Record.")
	if err := db.Delete("app", name); err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting database record")
	printSuccess(w, ("Application '" + name + "' successfully removed."))
	return true, nil
}

// DeployApplication : Deploys the application
func DeployApplication(w *os.File, name string) {
	printNormal(w, ("Deploying '" + name + "'."))
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return
	}
	path := filepath.Join(app.Path, "deploy")
	if err = os.RemoveAll(path); err != nil {
		printErr(w, err)
		return
	}
	printNormal(w, "Creating seperate directory for deployment.")
	_, err = git.PlainClone(path, false, &git.CloneOptions{
		URL: app.Repository,
	})
	if err != nil {
		printErr(w, err)
		return
	}
	printSuccess(w, "Creating seperate directory for deployment.")
	dock := Dockerfile{}
	proc := parseProcfile(path + "/Procfile")
	for _, el := range proc {
		if el.Name == "web" {
			commands := strings.Fields(el.Command)
			dock.Command = commands
		}
	}
	if fileExists(filepath.Join(path, "requirements.txt")) {
		printInfo(w, "Python was detected")
		app.Type = "python"
		if err := db.Write("app", name, app); err != nil {
			printErr(w, err)
			return
		}
	} else {
		printErr(w, "No type detected.")
		return
	}
	printNormal(w, "Detecting free port")
	l, _ := net.Listen("tcp", ":0")
	hostport := l.Addr().String()
	_, port, err := net.SplitHostPort(hostport)
	if err != nil {
		printErr(w, err)
		return
	}
	dock.Port = port
	l.Close()
	printInfo(w, "Port allocated: "+port)
	printNormal(w, "Creating Dockerfile")
	err = CreateDockerfile(dock, app)
	if err != nil {
		printErr(w, err)
		return
	}
	printSuccess(w, "Creating Dockerfile")
	printNormal(w, "Creating Docker image")
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return
	}

	session := sh.NewSession()
	session.Stdout = w
	session.Stderr = w
	session.SetDir(path + "/")
	_, err = session.Command("tar", "cvf", "../package.tar", ".").Output()
	if err != nil {
		printErr(w, err)
		return
	}
	f, err := os.Open(filepath.Join(path, "..", "package.tar"))
	if err != nil {
		printErr(w, err)
	}
	defer f.Close()

	imageBuildResponse, err := cli.ImageBuild(
		ctx,
		f,
		types.ImageBuildOptions{
			Tags:       []string{name},
			Dockerfile: "Dockerfile",
			Remove:     true})
	if err != nil {
		printErr(w, err)
	}
	type Event struct {
		Stream string `json:"stream"`
	}
	d := json.NewDecoder(imageBuildResponse.Body)
	var event *Event
	for {
		if err := d.Decode(&event); err != nil {
			if err == io.EOF {
				break
			}

			panic(err)
		}
		event.Stream = strings.TrimSuffix(event.Stream, "\n")
		if strings.Contains(event.Stream, "Step") {
			printInfo(w, event.Stream)
		}
	}
	defer imageBuildResponse.Body.Close()
	printSuccess(w, "Creating Docker image")
	printNormal(w, "Creating Container")
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: name,
		ExposedPorts: nat.PortSet{
			"5000/tcp": struct{}{},
		},
	}, &container.HostConfig{
		PortBindings: nat.PortMap{
			"5000/tcp": []nat.PortBinding{
				{
					HostIP:   "0.0.0.0",
					HostPort: port,
				},
			},
		}}, nil, name)
	if err != nil {
		printErr(w, err)
	}
	printSuccess(w, "Creating Container")
	printNormal(w, "Starting Container")
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		printErr(w, err)
	}

	printSuccess(w, "Starting Container")
	app.Running = true
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return
	}
}

// StopApplication : Stops the container of a application
func StopApplication(w *os.File, name string) error {
	printNormal(w, "Stopping Application '"+name+"'.")
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return err
	}
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return err
	}
	err = cli.ContainerStop(ctx, name, nil)
	if err != nil {
		printErr(w, err)
		return err
	}
	printSuccess(w, "Stopping Application '"+name+"'.")
	app.Running = false
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return err
	}
	return nil
}

// StartApplication : starts the application
func StartApplication(w *os.File, name string) error {
	printNormal(w, "Starting Application '"+name+"'.")
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return err
	}
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return err
	}
	err = cli.ContainerStart(ctx, name, types.ContainerStartOptions{})
	if err != nil {
		printErr(w, err)
		return err
	}
	printSuccess(w, "Starting Application '"+name+"'.")
	app.Running = true
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return err
	}
	return nil
}

// LogApplication : show the log oft the application
func LogApplication(w *os.File, name string, tail bool) {
	printNormal(w, "Logs of '"+name+"'.")
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return
	}
	out, err := cli.ContainerLogs(ctx, name, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Follow:     tail})
	if err != nil {
		printErr(w, err)
		return
	}
	io.Copy(os.Stdout, out)
}

// GetApplication : Get specific application
func GetApplication(name string) (Application, error) {
	app := Application{}
	if err := db.Read("app", name, &app); err != nil {
		printErr(os.Stdout, err)
		return Application{}, err
	}
	return app, nil
}

// GetApplications : Get a list of all applications
func GetApplications() ([]Application, error) {
	records, err := db.ReadAll("app")
	if err != nil {
		fmt.Println("Error", err)
		return []Application{}, err
	}
	applications := []Application{}
	for _, f := range records {
		appFound := Application{}
		if err := json.Unmarshal([]byte(f), &appFound); err != nil {
			fmt.Println("Error", err)
			return []Application{}, err
		}
		applications = append(applications, appFound)
	}
	return applications, nil
}
<commit_msg>+ docker api to remove containers<commit_after>package models

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"strings"

	sh "github.com/codeskyblue/go-sh"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	git "gopkg.in/src-d/go-git.v4"
)

// Application : stores information about the applications
type Application struct {
	Name       string `json:"name"`
	Running    bool   `json:"running"`
	Path       string `json:"path"`
	Repository string `json:"repo"`
	Type       string `json:"type"`
}

// CreateApplication : Creates a new Application
func CreateApplication(w *os.File, name string) (Application, error) {
	home := getHomeFolder()
	executable := getExecutablePath()
	basePath := filepath.Join(home, "PiaaS-Data")
	path := filepath.Join(basePath, "Applications", name, "repo")

	if _, err := os.Stat(path); err == nil {
		printErr(w, "App already exists.")
		return Application{}, err
	}
	printNormal(w, "Creating Application '"+name+"'.")
	printNormal(w, "Creating directories")
	err := os.MkdirAll(path, os.ModePerm)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Creating directories.")

	printNormal(w, "Initializing git repository.")
	if _, err := git.PlainInit(path, true); err != nil {
		printErr(w, err)
		return Application{}, err
	}
	err = os.MkdirAll(filepath.Join(path, "hooks"), os.ModePerm)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	file, err := os.Create(filepath.Join(path, "hooks", "post-receive"))
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	defer file.Close()
	fmt.Fprintf(file, "#!/usr/bin/env bash\n%s/PiaaS app deploy %s\n", executable, name)
	err = os.Chmod(filepath.Join(path, "hooks", "post-receive"), 0755)
	if err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Initializing git repository.")
	printInfo(w, "Repository path: "+path)

	app := Application{}
	app.Name = name
	app.Path = filepath.Join(basePath, "Applications", name)
	app.Repository = path
	printNormal(w, "Creating database record.")
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return Application{}, err
	}
	printSuccess(w, "Creating database record.")
	printSuccess(w, ("Application '" + name + "' successfully created."))
	return app, nil
}

// DeleteApplication : Deletes existing Application
func DeleteApplication(w *os.File, name string) (bool, error) {
	home := getHomeFolder()
	basePath := filepath.Join(home, "PiaaS-Data")
	path := filepath.Join(basePath, "Applications", name)
	if _, err := os.Stat(path); err != nil {
		printErr(w, "App does not exist.")
		return false, err
	}
	printNormal(w, ("Removing Application '" + name + "'."))
	printNormal(w, "Deleting Containers.")
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return false, err
	}
	err = cli.ContainerRemove(ctx, name, types.ContainerRemoveOptions{
		Force: true,
	})
	if err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting Images.")
	printNormal(w, "Deleting Images.")
	_, err = cli.ImageRemove(ctx, name, types.ImageRemoveOptions{Force: true})
	if err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting Images.")
	printNormal(w, "Removing Directories")
	if err := os.RemoveAll(path); err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Removing directories.")
	printNormal(w, "Deleting Database Record.")
	if err := db.Delete("app", name); err != nil {
		printErr(w, err)
		return false, err
	}
	printSuccess(w, "Deleting database record")
	printSuccess(w, ("Application '" + name + "' successfully removed."))
	return true, nil
}

// DeployApplication : Deploys the application
func DeployApplication(w *os.File, name string) {
	printNormal(w, ("Deploying '" + name + "'."))
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return
	}
	path := filepath.Join(app.Path, "deploy")
	if err = os.RemoveAll(path); err != nil {
		printErr(w, err)
		return
	}
	printNormal(w, "Creating seperate directory for deployment.")
	_, err = git.PlainClone(path, false, &git.CloneOptions{
		URL: app.Repository,
	})
	if err != nil {
		printErr(w, err)
		return
	}
	printSuccess(w, "Creating seperate directory for deployment.")
	dock := Dockerfile{}
	proc := parseProcfile(path + "/Procfile")
	for _, el := range proc {
		if el.Name == "web" {
			commands := strings.Fields(el.Command)
			dock.Command = commands
		}
	}
	if fileExists(filepath.Join(path, "requirements.txt")) {
		printInfo(w, "Python was detected")
		app.Type = "python"
		if err := db.Write("app", name, app); err != nil {
			printErr(w, err)
			return
		}
	} else {
		printErr(w, "No type detected.")
		return
	}
	printNormal(w, "Detecting free port")
	l, _ := net.Listen("tcp", ":0")
	hostport := l.Addr().String()
	_, port, err := net.SplitHostPort(hostport)
	if err != nil {
		printErr(w, err)
		return
	}
	dock.Port = port
	l.Close()
	printInfo(w, "Port allocated: "+port)
	printNormal(w, "Creating Dockerfile")
	err = CreateDockerfile(dock, app)
	if err != nil {
		printErr(w, err)
		return
	}
	printSuccess(w, "Creating Dockerfile")
	printNormal(w, "Creating Docker image")
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return
	}

	session := sh.NewSession()
	session.Stdout = w
	session.Stderr = w
	session.SetDir(path + "/")
	_, err = session.Command("tar", "cvf", "../package.tar", ".").Output()
	if err != nil {
		printErr(w, err)
		return
	}
	f, err := os.Open(filepath.Join(path, "..", "package.tar"))
	if err != nil {
		printErr(w, err)
	}
	defer f.Close()

	imageBuildResponse, err := cli.ImageBuild(
		ctx,
		f,
		types.ImageBuildOptions{
			Tags:       []string{name},
			Dockerfile: "Dockerfile",
			Remove:     true})
	if err != nil {
		printErr(w, err)
	}
	type Event struct {
		Stream string `json:"stream"`
	}
	d := json.NewDecoder(imageBuildResponse.Body)
	var event *Event
	for {
		if err := d.Decode(&event); err != nil {
			if err == io.EOF {
				break
			}

			panic(err)
		}
		event.Stream = strings.TrimSuffix(event.Stream, "\n")
		if strings.Contains(event.Stream, "Step") {
			printInfo(w, event.Stream)
		}
	}
	defer imageBuildResponse.Body.Close()
	printSuccess(w, "Creating Docker image")
	printNormal(w, "Creating Container")
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: name,
		ExposedPorts: nat.PortSet{
			"5000/tcp": struct{}{},
		},
	}, &container.HostConfig{
		PortBindings: nat.PortMap{
			"5000/tcp": []nat.PortBinding{
				{
					HostIP:   "0.0.0.0",
					HostPort: port,
				},
			},
		}}, nil, name)
	if err != nil {
		printErr(w, err)
	}
	printSuccess(w, "Creating Container")
	printNormal(w, "Starting Container")
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		printErr(w, err)
	}

	printSuccess(w, "Starting Container")
	app.Running = true
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return
	}
}

// StopApplication : Stops the container of a application
func StopApplication(w *os.File, name string) error {
	printNormal(w, "Stopping Application '"+name+"'.")
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return err
	}
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return err
	}
	err = cli.ContainerStop(ctx, name, nil)
	if err != nil {
		printErr(w, err)
		return err
	}
	printSuccess(w, "Stopping Application '"+name+"'.")
	app.Running = false
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return err
	}
	return nil
}

// StartApplication : starts the application
func StartApplication(w *os.File, name string) error {
	printNormal(w, "Starting Application '"+name+"'.")
	app, err := GetApplication(name)
	if err != nil {
		printErr(w, err)
		return err
	}
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return err
	}
	err = cli.ContainerStart(ctx, name, types.ContainerStartOptions{})
	if err != nil {
		printErr(w, err)
		return err
	}
	printSuccess(w, "Starting Application '"+name+"'.")
	app.Running = true
	if err := db.Write("app", name, app); err != nil {
		printErr(w, err)
		return err
	}
	return nil
}

// LogApplication : show the log oft the application
func LogApplication(w *os.File, name string, tail bool) {
	printNormal(w, "Logs of '"+name+"'.")
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		printErr(w, err)
		return
	}
	out, err := cli.ContainerLogs(ctx, name, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Follow:     tail})
	if err != nil {
		printErr(w, err)
		return
	}
	io.Copy(os.Stdout, out)
}

// GetApplication : Get specific application
func GetApplication(name string) (Application, error) {
	app := Application{}
	if err := db.Read("app", name, &app); err != nil {
		printErr(os.Stdout, err)
		return Application{}, err
	}
	return app, nil
}

// GetApplications : Get a list of all applications
func GetApplications() ([]Application, error) {
	records, err := db.ReadAll("app")
	if err != nil {
		fmt.Println("Error", err)
		return []Application{}, err
	}
	applications := []Application{}
	for _, f := range records {
		appFound := Application{}
		if err := json.Unmarshal([]byte(f), &appFound); err != nil {
			fmt.Println("Error", err)
			return []Application{}, err
		}
		applications = append(applications, appFound)
	}
	return applications, nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"errors"
	"github.com/trasa/watchmud-message"
	"github.com/trasa/watchmud-message/slot"
	"github.com/trasa/watchmud/client"
	"github.com/trasa/watchmud/db"
	"github.com/trasa/watchmud/gameserver"
	"github.com/trasa/watchmud/mudtime"
	player2 "github.com/trasa/watchmud/player"
	"github.com/trasa/watchmud/world"
	"log"
	"time"
)

type GameServer struct {
	incomingBuffer chan *gameserver.HandlerParameter
	World          *world.World
	tickInterval   time.Duration
}

func NewGameServer(worldFilesDirectory string) (gs *GameServer, err error) {
	w, err := world.New(worldFilesDirectory)
	gs = &GameServer{
		incomingBuffer: make(chan *gameserver.HandlerParameter),
		World:          w,
	}
	return
}

//noinspection SpellCheckingInspection
func (gs *GameServer) Run() {
	// this is the loop that handles incoming requests
	// needs to be organized around PULSEs
	tstart := time.Now().UnixNano()
	ticker := time.NewTicker(mudtime.PULSE_INTERVAL)
	pulse := mudtime.PulseCount(0)

	for {
		select {
		case <-ticker.C:
			now := time.Now().UnixNano()
			deltaSeconds := float64(now-tstart) / 1000000000
			tstart = now
			pulse++
			gs.heartbeat(pulse, deltaSeconds)
		}
	}
}

// runs the heartbeat of the game. Use pulse to determine intervals
// between things (ex. reset zones every 15 minutes...)
// delta is the amount of time since the last heartbeat was run.
func (gs *GameServer) heartbeat(pulse mudtime.PulseCount, delta float64) {
	//log.Printf("pulse %d hb %d", pulse, delta)
	// mobs, scripts, ...

	// pulse zone
	// (zone reset ...)
	if pulse.CheckInterval(mudtime.PULSE_ZONE) {
		gs.World.DoZoneActivity()
	}

	// pulse mobs
	// (mobs walk around, initiate attack?)
	if pulse.CheckInterval(mudtime.PULSE_MOBILE) {
		gs.World.DoMobileActivity()
	}

	// perform violence
	// do the attacking (players and mobs and everybody)
	if pulse.CheckInterval(mudtime.PULSE_VIOLENCE) {
		gs.World.DoViolence(pulse)
	}

	// mud-hour ("player tick")
	// affect weather, regen ..

	// handle an incoming message if one exists
	// TODO tick time: figure out how many incoming messages we can handle
	// see issue #4
	// for now, just process until buffer is empty...

	// not really infinite as the method will return false if there was
	// nothing to do.
	//noinspection GoInfiniteFor
	for gs.processIncomingMessage() {
	}
}

// read a message off of incomingMessageBuffer and do it
// this doesn't block so if the buffer is empty, the method returns immediately
// If a message was procssed (even in error) return true.
// Otherwise return false.
func (gs *GameServer) processIncomingMessage() bool {
	received := false
	select {
	case msg := <-gs.incomingBuffer:
		received = true
		switch msg.Message.Inner.(type) {
		case *message.GameMessage_LoginRequest:
			err := gs.handleLogin(msg) // TODO error handling
			if err != nil {
				log.Printf("Error from handleLogin: %v", err)
			}

		case *message.GameMessage_CreatePlayerRequest:
			err := gs.handleCreatePlayer(msg)
			if err != nil {
				log.Printf("Error from handleCreatePlayer: %v", err)
			}

		case *message.GameMessage_DataRequest:
			err := gs.handleDataRequest(msg)
			if err != nil {
				log.Printf("Error from handleDataRequest: %v", err)
			}

		default:
			gs.World.HandleIncomingMessage(msg)
		}
	default:
		// do nothing
	}
	return received
}

func (gs *GameServer) Receive(msg *gameserver.HandlerParameter) {
	gs.incomingBuffer <- msg
}

func (gs *GameServer) Logout(c client.Client, cause string) {
	gm, err := message.NewGameMessage(message.LogoutRequest{Cause: cause})
	if err != nil {
		log.Printf("Error creating GameMessage for LogoutRequest: %v", err)
	} else {
		gs.Receive(gameserver.NewHandlerParameter(c, gm))
	}
}

func (gs *GameServer) handleLogin(msg *gameserver.HandlerParameter) (err error) {
	// is this connection already authenticated?
	// see if we can find an existing player ..
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	// what if player is logged in on a different client?
	// TODO
	/*
		p := FindPlayerByClient(message.Client)
		if p != nil {
			// already authenticated, can't login again
			// TODO
			// note that this isn't really working; the same username can log on twice
			// instead the old player should be kicked and the new player take over
			p.Send(LoginResponse{
				Response: Response{
					MessageType: "login_response",
					Successful:  false,
					ResultCode:  "ALREADY_AUTHENTICATED",
				},
			})
			return
		}
	*/

	// todo authentication and stuff - does GRPC have a built in authentication method?

	playerName := msg.Message.GetLoginRequest().PlayerName
	playerData, err := db.GetPlayerData(playerName)
	if err != nil {
		log.Printf("Error trying to retrieve playerData for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_DATA_ERROR",
		})
		if clientErr != nil {
			log.Printf("client error trying to send PLAYER_DATA_ERROR on login: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(msg.Message.GetLoginRequest().PlayerName, &playerData, msg.Client)

	// load inventory: have to convert playerinventorydata into
	// instances and definitions here, because we need 'the world' to do it.
	for _, inv := range playerData.Inventory {
		inst, err := gs.World.CreateObjectInstance(inv.ZoneId, inv.DefinitionId, inv.InstanceId)
		if err != nil {
			log.Printf("Error trying to load player %d (%s) inventory instance (%s-%s-%s) -- %s", playerData.Id, playerName, inv.ZoneId, inv.DefinitionId, inv.InstanceId, err)
			clientErr := msg.Client.Send(message.LoginResponse{
				Success:    false,
				ResultCode: "PLAYER_INVENTORY_DATA_ERROR",
			})
			if clientErr != nil {
				log.Printf("client error trying to send PLAYER_INVENTORY_DATA_ERROR on login: %v", clientErr)
			}
			return err
		}
		player.LoadInventory(inst)

	}
	// slots - need inventory before we can set slots
	for _, sd := range playerData.Slots.Slots {
		inst, exists := player.GetInventory().GetByInstanceId(sd.InstanceId)
		if !exists {
			log.Printf("Error trying to load player %d (%s) slot: %d object instance doesn't exist in inventory: %s",
				playerData.Id, playerName, sd.Location, sd.InstanceId)
		} else {
			player.Slots().Set(slot.Location(sd.Location), inst)
		}
	}

	msg.Client.SetPlayer(player)
	msg.Player = player

	// add player to world
	gs.World.AddPlayer(player)

	err = player.Send(message.LoginResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

func (gs *GameServer) handleCreatePlayer(msg *gameserver.HandlerParameter) (err error) {
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	req := msg.Message.GetCreatePlayerRequest()
	playerName := req.PlayerName

	// create player data for playerName
	// TODO for now, just set abilities to some defaults
	abilities := player2.Abilities{
		Strength:     15,
		Dexterity:    15,
		Constitution: 15,
		Intelligence: 15,
		Wisdom:       15,
		Charisma:     15,
	}
	playerData, err := db.CreatePlayerData(playerName, req.Race, req.Class, gs.World.StartRoom.Zone.Id, gs.World.StartRoom.Id, abilities)
	if err != nil {
		log.Printf("Error trying to create player for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_EXISTS",
		})
		if clientErr != nil {
			log.Printf("Client error trying to send PLAYER_ALREADY_EXISTS message: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(playerName, playerData, msg.Client)
	msg.Client.SetPlayer(player)
	msg.Player = player

	gs.World.AddPlayer(player) // TODO destination
	err = player.Send(message.CreatePlayerResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

// The client is requesting game data: races, class definitions, something like that.
func (gs *GameServer) handleDataRequest(msg *gameserver.HandlerParameter) (err error) {
	req := msg.Message.GetDataRequest()
	switch req.DataType {
	case "races":
		err = sendRaceData(msg)

	default:
		err = errors.New("Unhandled DataRequest Type: " + req.DataType)
	}
	return
}

// Send Race data to the client
func sendRaceData(msg *gameserver.HandlerParameter) (err error) {
	// get from db
	races, err := db.GetRaceData()
	if err != nil {
		if clientErr := msg.Client.Send(message.DataResponse{
			Success:    false,
			ResultCode: "DB_ERROR",
		}); clientErr != nil {
			log.Printf("handleDataRequest failed to send DB_ERROR for 'races' request: %v", clientErr)
		}
		return
	}
	// serialize races
	if racejson, err := json.Marshal(races); err != nil {
		if clientErr := msg.Client.Send(message.DataResponse{
			Success:    true,
			ResultCode: "OK",
			Data:       racejson,
		}); clientErr != nil {
			log.Printf("handleDataRequest failed to send race data: %v", clientErr)
		}
	}
	return
}
<commit_msg>serialize race data works now<commit_after>package server

import (
	"encoding/json"
	"errors"
	"github.com/trasa/watchmud-message"
	"github.com/trasa/watchmud-message/slot"
	"github.com/trasa/watchmud/client"
	"github.com/trasa/watchmud/db"
	"github.com/trasa/watchmud/gameserver"
	"github.com/trasa/watchmud/mudtime"
	player2 "github.com/trasa/watchmud/player"
	"github.com/trasa/watchmud/world"
	"log"
	"time"
)

type GameServer struct {
	incomingBuffer chan *gameserver.HandlerParameter
	World          *world.World
	tickInterval   time.Duration
}

func NewGameServer(worldFilesDirectory string) (gs *GameServer, err error) {
	w, err := world.New(worldFilesDirectory)
	gs = &GameServer{
		incomingBuffer: make(chan *gameserver.HandlerParameter),
		World:          w,
	}
	return
}

//noinspection SpellCheckingInspection
func (gs *GameServer) Run() {
	// this is the loop that handles incoming requests
	// needs to be organized around PULSEs
	tstart := time.Now().UnixNano()
	ticker := time.NewTicker(mudtime.PULSE_INTERVAL)
	pulse := mudtime.PulseCount(0)

	for {
		select {
		case <-ticker.C:
			now := time.Now().UnixNano()
			deltaSeconds := float64(now-tstart) / 1000000000
			tstart = now
			pulse++
			gs.heartbeat(pulse, deltaSeconds)
		}
	}
}

// runs the heartbeat of the game. Use pulse to determine intervals
// between things (ex. reset zones every 15 minutes...)
// delta is the amount of time since the last heartbeat was run.
func (gs *GameServer) heartbeat(pulse mudtime.PulseCount, delta float64) {
	//log.Printf("pulse %d hb %d", pulse, delta)
	// mobs, scripts, ...

	// pulse zone
	// (zone reset ...)
	if pulse.CheckInterval(mudtime.PULSE_ZONE) {
		gs.World.DoZoneActivity()
	}

	// pulse mobs
	// (mobs walk around, initiate attack?)
	if pulse.CheckInterval(mudtime.PULSE_MOBILE) {
		gs.World.DoMobileActivity()
	}

	// perform violence
	// do the attacking (players and mobs and everybody)
	if pulse.CheckInterval(mudtime.PULSE_VIOLENCE) {
		gs.World.DoViolence(pulse)
	}

	// mud-hour ("player tick")
	// affect weather, regen ..

	// handle an incoming message if one exists
	// TODO tick time: figure out how many incoming messages we can handle
	// see issue #4
	// for now, just process until buffer is empty...

	// not really infinite as the method will return false if there was
	// nothing to do.
	//noinspection GoInfiniteFor
	for gs.processIncomingMessage() {
	}
}

// read a message off of incomingMessageBuffer and do it
// this doesn't block so if the buffer is empty, the method returns immediately
// If a message was procssed (even in error) return true.
// Otherwise return false.
func (gs *GameServer) processIncomingMessage() bool {
	received := false
	select {
	case msg := <-gs.incomingBuffer:
		received = true
		switch msg.Message.Inner.(type) {
		case *message.GameMessage_LoginRequest:
			err := gs.handleLogin(msg) // TODO error handling
			if err != nil {
				log.Printf("Error from handleLogin: %v", err)
			}

		case *message.GameMessage_CreatePlayerRequest:
			err := gs.handleCreatePlayer(msg)
			if err != nil {
				log.Printf("Error from handleCreatePlayer: %v", err)
			}

		case *message.GameMessage_DataRequest:
			err := gs.handleDataRequest(msg)
			if err != nil {
				log.Printf("Error from handleDataRequest: %v", err)
			}

		default:
			gs.World.HandleIncomingMessage(msg)
		}
	default:
		// do nothing
	}
	return received
}

func (gs *GameServer) Receive(msg *gameserver.HandlerParameter) {
	gs.incomingBuffer <- msg
}

func (gs *GameServer) Logout(c client.Client, cause string) {
	gm, err := message.NewGameMessage(message.LogoutRequest{Cause: cause})
	if err != nil {
		log.Printf("Error creating GameMessage for LogoutRequest: %v", err)
	} else {
		gs.Receive(gameserver.NewHandlerParameter(c, gm))
	}
}

func (gs *GameServer) handleLogin(msg *gameserver.HandlerParameter) (err error) {
	// is this connection already authenticated?
	// see if we can find an existing player ..
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	// what if player is logged in on a different client?
	// TODO
	/*
		p := FindPlayerByClient(message.Client)
		if p != nil {
			// already authenticated, can't login again
			// TODO
			// note that this isn't really working; the same username can log on twice
			// instead the old player should be kicked and the new player take over
			p.Send(LoginResponse{
				Response: Response{
					MessageType: "login_response",
					Successful:  false,
					ResultCode:  "ALREADY_AUTHENTICATED",
				},
			})
			return
		}
	*/

	// todo authentication and stuff - does GRPC have a built in authentication method?

	playerName := msg.Message.GetLoginRequest().PlayerName
	playerData, err := db.GetPlayerData(playerName)
	if err != nil {
		log.Printf("Error trying to retrieve playerData for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.LoginResponse{
			Success:    false,
			ResultCode: "PLAYER_DATA_ERROR",
		})
		if clientErr != nil {
			log.Printf("client error trying to send PLAYER_DATA_ERROR on login: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(msg.Message.GetLoginRequest().PlayerName, &playerData, msg.Client)

	// load inventory: have to convert playerinventorydata into
	// instances and definitions here, because we need 'the world' to do it.
	for _, inv := range playerData.Inventory {
		inst, err := gs.World.CreateObjectInstance(inv.ZoneId, inv.DefinitionId, inv.InstanceId)
		if err != nil {
			log.Printf("Error trying to load player %d (%s) inventory instance (%s-%s-%s) -- %s", playerData.Id, playerName, inv.ZoneId, inv.DefinitionId, inv.InstanceId, err)
			clientErr := msg.Client.Send(message.LoginResponse{
				Success:    false,
				ResultCode: "PLAYER_INVENTORY_DATA_ERROR",
			})
			if clientErr != nil {
				log.Printf("client error trying to send PLAYER_INVENTORY_DATA_ERROR on login: %v", clientErr)
			}
			return err
		}
		player.LoadInventory(inst)

	}
	// slots - need inventory before we can set slots
	for _, sd := range playerData.Slots.Slots {
		inst, exists := player.GetInventory().GetByInstanceId(sd.InstanceId)
		if !exists {
			log.Printf("Error trying to load player %d (%s) slot: %d object instance doesn't exist in inventory: %s",
				playerData.Id, playerName, sd.Location, sd.InstanceId)
		} else {
			player.Slots().Set(slot.Location(sd.Location), inst)
		}
	}

	msg.Client.SetPlayer(player)
	msg.Player = player

	// add player to world
	gs.World.AddPlayer(player)

	err = player.Send(message.LoginResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

func (gs *GameServer) handleCreatePlayer(msg *gameserver.HandlerParameter) (err error) {
	if msg.Client.GetPlayer() != nil {
		// you've already got one
		err = msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_ATTACHED",
		})
		return
	}
	req := msg.Message.GetCreatePlayerRequest()
	playerName := req.PlayerName

	// create player data for playerName
	// TODO for now, just set abilities to some defaults
	abilities := player2.Abilities{
		Strength:     15,
		Dexterity:    15,
		Constitution: 15,
		Intelligence: 15,
		Wisdom:       15,
		Charisma:     15,
	}
	playerData, err := db.CreatePlayerData(playerName, req.Race, req.Class, gs.World.StartRoom.Zone.Id, gs.World.StartRoom.Id, abilities)
	if err != nil {
		log.Printf("Error trying to create player for %s: %v", playerName, err)
		clientErr := msg.Client.Send(message.CreatePlayerResponse{
			Success:    false,
			ResultCode: "PLAYER_ALREADY_EXISTS",
		})
		if clientErr != nil {
			log.Printf("Client error trying to send PLAYER_ALREADY_EXISTS message: %v", clientErr)
		}
		return // err
	}
	player := NewClientPlayerFromPlayerData(playerName, playerData, msg.Client)
	msg.Client.SetPlayer(player)
	msg.Player = player

	gs.World.AddPlayer(player) // TODO destination
	err = player.Send(message.CreatePlayerResponse{
		Success:    true,
		ResultCode: "OK",
		PlayerName: player.GetName(),
	})
	return
}

// The client is requesting game data: races, class definitions, something like that.
func (gs *GameServer) handleDataRequest(msg *gameserver.HandlerParameter) (err error) {
	req := msg.Message.GetDataRequest()
	switch req.DataType {
	case "races":
		err = sendRaceData(msg)

	default:
		err = errors.New("Unhandled DataRequest Type: " + req.DataType)
	}
	return
}

// Send Race data to the client
func sendRaceData(msg *gameserver.HandlerParameter) (err error) {
	// get from db
	races, err := db.GetRaceData()
	if err != nil {
		if clientErr := msg.Client.Send(message.DataResponse{
			Success:    false,
			ResultCode: "DB_ERROR",
		}); clientErr != nil {
			log.Printf("handleDataRequest failed to send DB_ERROR for 'races' request: %v", clientErr)
		}
		return
	}
	// serialize races
	racejson, err := json.Marshal(races)
	if err != nil {
		log.Printf("Serialize failed: %v", err)
		if clientErr := msg.Client.Send(message.DataResponse{
			Success:    false,
			ResultCode: "SERIALIZE_FAILED",
		}); clientErr != nil {
			log.Printf("handleDataRequest Error failed to send race data: %v, %v", err, clientErr)
		}
	} else if clientErr := msg.Client.Send(message.DataResponse{
		Success:    true,
		ResultCode: "OK",
		Data:       racejson,
	}); clientErr != nil {
		log.Printf("handleDataRequest failed to send race data: %v", clientErr)
	}
	return
}
<|endoftext|>