<commit_before>/*
Copyright 2014 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 nodeipam

import (
	"net"
	"time"

	"k8s.io/klog"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	v1 "k8s.io/api/core/v1"
	coreinformers "k8s.io/client-go/informers/core/v1"
	clientset "k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	cloudprovider "k8s.io/cloud-provider"
	"k8s.io/component-base/metrics/prometheus/ratelimiter"
	"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
)

const (
	// ipamResyncInterval is the amount of time between when the cloud and node
	// CIDR range assignments are synchronized.
	ipamResyncInterval = 30 * time.Second
	// ipamMaxBackoff is the maximum backoff for retrying synchronization of a
	// given in the error state.
	ipamMaxBackoff = 10 * time.Second
	// ipamInitialRetry is the initial retry interval for retrying synchronization of a
	// given in the error state.
	ipamInitialBackoff = 250 * time.Millisecond
)

// Controller is the controller that manages node ipam state.
type Controller struct {
	allocatorType ipam.CIDRAllocatorType

	cloud                cloudprovider.Interface
	clusterCIDRs         []*net.IPNet
	serviceCIDR          *net.IPNet
	secondaryServiceCIDR *net.IPNet
	kubeClient           clientset.Interface
	// Method for easy mocking in unittest.
	lookupIP func(host string) ([]net.IP, error)

	nodeLister         corelisters.NodeLister
	nodeInformerSynced cache.InformerSynced

	cidrAllocator ipam.CIDRAllocator

	forcefullyDeletePod func(*v1.Pod) error
}

// NewNodeIpamController returns a new node IP Address Management controller to
// sync instances from cloudprovider.
// This method returns an error if it is unable to initialize the CIDR bitmap with
// podCIDRs it has already allocated to nodes. Since we don't allow podCIDR changes
// currently, this should be handled as a fatal error.
func NewNodeIpamController(
	nodeInformer coreinformers.NodeInformer,
	cloud cloudprovider.Interface,
	kubeClient clientset.Interface,
	clusterCIDRs []*net.IPNet,
	serviceCIDR *net.IPNet,
	secondaryServiceCIDR *net.IPNet,
	nodeCIDRMaskSize int,
	allocatorType ipam.CIDRAllocatorType) (*Controller, error) {

	if kubeClient == nil {
		klog.Fatalf("kubeClient is nil when starting Controller")
	}

	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(klog.Infof)

	klog.Infof("Sending events to api server.")
	eventBroadcaster.StartRecordingToSink(
		&v1core.EventSinkImpl{
			Interface: kubeClient.CoreV1().Events(""),
		})

	if kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil {
		ratelimiter.RegisterMetricAndTrackRateLimiterUsage("node_ipam_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter())
	}

	// Cloud CIDR allocator does not rely on clusterCIDR or nodeCIDRMaskSize for allocation.
	if allocatorType != ipam.CloudAllocatorType {
		if len(clusterCIDRs) == 0 {
			klog.Fatal("Controller: Must specify --cluster-cidr if --allocate-node-cidrs is set")
		}

		// TODO: (khenidak) IPv6DualStack beta:
		// - modify mask to allow flexible masks for IPv4 and IPv6
		// - for alpha status they are the same

		// for each cidr, node mask size must be < cidr mask
		for _, cidr := range clusterCIDRs {
			mask := cidr.Mask
			if maskSize, _ := mask.Size(); maskSize > nodeCIDRMaskSize {
				klog.Fatal("Controller: Invalid --cluster-cidr, mask size of cluster CIDR must be less than --node-cidr-mask-size")
			}
		}
	}

	ic := &Controller{
		cloud:                cloud,
		kubeClient:           kubeClient,
		lookupIP:             net.LookupIP,
		clusterCIDRs:         clusterCIDRs,
		serviceCIDR:          serviceCIDR,
		secondaryServiceCIDR: secondaryServiceCIDR,
		allocatorType:        allocatorType,
	}

	// TODO: Abstract this check into a generic controller manager should run method.
	if ic.allocatorType == ipam.IPAMFromClusterAllocatorType || ic.allocatorType == ipam.IPAMFromCloudAllocatorType {
		startLegacyIPAM(ic, nodeInformer, cloud, kubeClient, clusterCIDRs, serviceCIDR, nodeCIDRMaskSize)
	} else {
		var err error
		ic.cidrAllocator, err = ipam.New(kubeClient, cloud, nodeInformer, ic.allocatorType, clusterCIDRs, ic.serviceCIDR, ic.secondaryServiceCIDR, nodeCIDRMaskSize)
		if err != nil {
			return nil, err
		}
	}

	ic.nodeLister = nodeInformer.Lister()
	ic.nodeInformerSynced = nodeInformer.Informer().HasSynced

	return ic, nil
}

// Run starts an asynchronous loop that monitors the status of cluster nodes.
func (nc *Controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	klog.Infof("Starting ipam controller")
	defer klog.Infof("Shutting down ipam controller")

	if !cache.WaitForNamedCacheSync("node", stopCh, nc.nodeInformerSynced) {
		return
	}

	if nc.allocatorType != ipam.IPAMFromClusterAllocatorType && nc.allocatorType != ipam.IPAMFromCloudAllocatorType {
		go nc.cidrAllocator.Run(stopCh)
	}

	<-stopCh
}
<commit_msg>fix log and annotation :cidr mask size must be <= node mask<commit_after>/*
Copyright 2014 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 nodeipam

import (
	"net"
	"time"

	"k8s.io/klog"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	v1 "k8s.io/api/core/v1"
	coreinformers "k8s.io/client-go/informers/core/v1"
	clientset "k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	cloudprovider "k8s.io/cloud-provider"
	"k8s.io/component-base/metrics/prometheus/ratelimiter"
	"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
)

const (
	// ipamResyncInterval is the amount of time between when the cloud and node
	// CIDR range assignments are synchronized.
	ipamResyncInterval = 30 * time.Second
	// ipamMaxBackoff is the maximum backoff for retrying synchronization of a
	// given in the error state.
	ipamMaxBackoff = 10 * time.Second
	// ipamInitialRetry is the initial retry interval for retrying synchronization of a
	// given in the error state.
	ipamInitialBackoff = 250 * time.Millisecond
)

// Controller is the controller that manages node ipam state.
type Controller struct {
	allocatorType ipam.CIDRAllocatorType

	cloud                cloudprovider.Interface
	clusterCIDRs         []*net.IPNet
	serviceCIDR          *net.IPNet
	secondaryServiceCIDR *net.IPNet
	kubeClient           clientset.Interface
	// Method for easy mocking in unittest.
	lookupIP func(host string) ([]net.IP, error)

	nodeLister         corelisters.NodeLister
	nodeInformerSynced cache.InformerSynced

	cidrAllocator ipam.CIDRAllocator

	forcefullyDeletePod func(*v1.Pod) error
}

// NewNodeIpamController returns a new node IP Address Management controller to
// sync instances from cloudprovider.
// This method returns an error if it is unable to initialize the CIDR bitmap with
// podCIDRs it has already allocated to nodes. Since we don't allow podCIDR changes
// currently, this should be handled as a fatal error.
func NewNodeIpamController(
	nodeInformer coreinformers.NodeInformer,
	cloud cloudprovider.Interface,
	kubeClient clientset.Interface,
	clusterCIDRs []*net.IPNet,
	serviceCIDR *net.IPNet,
	secondaryServiceCIDR *net.IPNet,
	nodeCIDRMaskSize int,
	allocatorType ipam.CIDRAllocatorType) (*Controller, error) {

	if kubeClient == nil {
		klog.Fatalf("kubeClient is nil when starting Controller")
	}

	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(klog.Infof)

	klog.Infof("Sending events to api server.")
	eventBroadcaster.StartRecordingToSink(
		&v1core.EventSinkImpl{
			Interface: kubeClient.CoreV1().Events(""),
		})

	if kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil {
		ratelimiter.RegisterMetricAndTrackRateLimiterUsage("node_ipam_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter())
	}

	// Cloud CIDR allocator does not rely on clusterCIDR or nodeCIDRMaskSize for allocation.
	if allocatorType != ipam.CloudAllocatorType {
		if len(clusterCIDRs) == 0 {
			klog.Fatal("Controller: Must specify --cluster-cidr if --allocate-node-cidrs is set")
		}

		// TODO: (khenidak) IPv6DualStack beta:
		// - modify mask to allow flexible masks for IPv4 and IPv6
		// - for alpha status they are the same

		// for each cidr, cidr mask size must be <= node mask
		for _, cidr := range clusterCIDRs {
			mask := cidr.Mask
			if maskSize, _ := mask.Size(); maskSize > nodeCIDRMaskSize {
				klog.Fatal("Controller: Invalid --cluster-cidr, mask size of cluster CIDR must be less than or equal to --node-cidr-mask-size")
			}
		}
	}

	ic := &Controller{
		cloud:                cloud,
		kubeClient:           kubeClient,
		lookupIP:             net.LookupIP,
		clusterCIDRs:         clusterCIDRs,
		serviceCIDR:          serviceCIDR,
		secondaryServiceCIDR: secondaryServiceCIDR,
		allocatorType:        allocatorType,
	}

	// TODO: Abstract this check into a generic controller manager should run method.
	if ic.allocatorType == ipam.IPAMFromClusterAllocatorType || ic.allocatorType == ipam.IPAMFromCloudAllocatorType {
		startLegacyIPAM(ic, nodeInformer, cloud, kubeClient, clusterCIDRs, serviceCIDR, nodeCIDRMaskSize)
	} else {
		var err error
		ic.cidrAllocator, err = ipam.New(kubeClient, cloud, nodeInformer, ic.allocatorType, clusterCIDRs, ic.serviceCIDR, ic.secondaryServiceCIDR, nodeCIDRMaskSize)
		if err != nil {
			return nil, err
		}
	}

	ic.nodeLister = nodeInformer.Lister()
	ic.nodeInformerSynced = nodeInformer.Informer().HasSynced

	return ic, nil
}

// Run starts an asynchronous loop that monitors the status of cluster nodes.
func (nc *Controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	klog.Infof("Starting ipam controller")
	defer klog.Infof("Shutting down ipam controller")

	if !cache.WaitForNamedCacheSync("node", stopCh, nc.nodeInformerSynced) {
		return
	}

	if nc.allocatorType != ipam.IPAMFromClusterAllocatorType && nc.allocatorType != ipam.IPAMFromCloudAllocatorType {
		go nc.cidrAllocator.Run(stopCh)
	}

	<-stopCh
}
<|endoftext|>


<commit_before>package rados

// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"

import "unsafe"
import "bytes"
import "fmt"

// ClusterStat represents Ceph cluster statistics.
type ClusterStat struct {
	Kb          uint64
	Kb_used     uint64
	Kb_avail    uint64
	Num_objects uint64
}

// Conn is a connection handle to a Ceph cluster.
type Conn struct {
	cluster   C.rados_t
	connected bool
}

// PingMonitor sends a ping to a monitor and returns the reply.
func (c *Conn) PingMonitor(id string) (string, error) {
	c_id := C.CString(id)
	defer C.free(unsafe.Pointer(c_id))

	var strlen C.size_t
	var strout *C.char

	ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
	defer C.rados_buffer_free(strout)

	if ret == 0 {
		reply := C.GoStringN(strout, (C.int)(strlen))
		return reply, nil
	} else {
		return "", RadosError(int(ret))
	}
}

// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
	ret := C.rados_connect(c.cluster)
	if ret != 0 {
		return RadosError(int(ret))
	}
	c.connected = true
	return nil
}

// Shutdown disconnects from the cluster.
func (c *Conn) Shutdown() {
	if err := c.ensure_connected(); err != nil {
		return
	}
	freeConn(c)
}

// ReadConfigFile configures the connection using a Ceph configuration file.
func (c *Conn) ReadConfigFile(path string) error {
	c_path := C.CString(path)
	defer C.free(unsafe.Pointer(c_path))
	ret := C.rados_conf_read_file(c.cluster, c_path)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

// ReadDefaultConfigFile configures the connection using a Ceph configuration
// file located at default locations.
func (c *Conn) ReadDefaultConfigFile() error {
	ret := C.rados_conf_read_file(c.cluster, nil)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
	c_pool := C.CString(pool)
	defer C.free(unsafe.Pointer(c_pool))
	ioctx := &IOContext{}
	ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
	if ret == 0 {
		return ioctx, nil
	} else {
		return nil, RadosError(int(ret))
	}
}

// ListPools returns the names of all existing pools.
func (c *Conn) ListPools() (names []string, err error) {
	buf := make([]byte, 4096)
	for {
		ret := int(C.rados_pool_list(c.cluster,
			(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
		if ret < 0 {
			return nil, RadosError(int(ret))
		}

		if ret > len(buf) {
			buf = make([]byte, ret)
			continue
		}

		tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
		for _, s := range tmp {
			if len(s) > 0 {
				name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
				names = append(names, name)
			}
		}

		return names, nil
	}
}

// SetConfigOption sets the value of the configuration option identified by
// the given name.
func (c *Conn) SetConfigOption(option, value string) error {
	c_opt, c_val := C.CString(option), C.CString(value)
	defer C.free(unsafe.Pointer(c_opt))
	defer C.free(unsafe.Pointer(c_val))
	ret := C.rados_conf_set(c.cluster, c_opt, c_val)
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

// GetConfigOption returns the value of the Ceph configuration option
// identified by the given name.
func (c *Conn) GetConfigOption(name string) (value string, err error) {
	buf := make([]byte, 4096)
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_conf_get(c.cluster, c_name,
		(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	// FIXME: ret may be -ENAMETOOLONG if the buffer is not large enough. We
	// can handle this case, but we need a reliable way to test for
	// -ENAMETOOLONG constant. Will the syscall/Errno stuff in Go help?
	if ret == 0 {
		value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
		return value, nil
	} else {
		return "", RadosError(ret)
	}
}

// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
// retrieved.
func (c *Conn) WaitForLatestOSDMap() error {
	ret := C.rados_wait_for_latest_osdmap(c.cluster)
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

func (c *Conn) ensure_connected() error {
	if c.connected {
		return nil
	} else {
		return RadosError(1)
	}
}

// GetClusterStats returns statistics about the cluster associated with the
// connection.
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
	if err := c.ensure_connected(); err != nil {
		return ClusterStat{}, err
	}
	c_stat := C.struct_rados_cluster_stat_t{}
	ret := C.rados_cluster_stat(c.cluster, &c_stat)
	if ret < 0 {
		return ClusterStat{}, RadosError(int(ret))
	} else {
		return ClusterStat{
			Kb:          uint64(c_stat.kb),
			Kb_used:     uint64(c_stat.kb_used),
			Kb_avail:    uint64(c_stat.kb_avail),
			Num_objects: uint64(c_stat.num_objects),
		}, nil
	}
}

// ParseCmdLineArgs configures the connection from command line arguments.
func (c *Conn) ParseCmdLineArgs(args []string) error {
	// add an empty element 0 -- Ceph treats the array as the actual contents
	// of argv and skips the first element (the executable name)
	argc := C.int(len(args) + 1)
	argv := make([]*C.char, argc)

	// make the first element a string just in case it is ever examined
	argv[0] = C.CString("placeholder")
	defer C.free(unsafe.Pointer(argv[0]))

	for i, arg := range args {
		argv[i+1] = C.CString(arg)
		defer C.free(unsafe.Pointer(argv[i+1]))
	}

	ret := C.rados_conf_parse_argv(c.cluster, argc, &argv[0])
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

// ParseDefaultConfigEnv configures the connection from the default Ceph
// environment variable(s).
func (c *Conn) ParseDefaultConfigEnv() error {
	ret := C.rados_conf_parse_env(c.cluster, nil)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
// is a unique identifier of an entire Ceph cluster.
func (c *Conn) GetFSID() (fsid string, err error) {
	buf := make([]byte, 37)
	ret := int(C.rados_cluster_fsid(c.cluster,
		(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	// FIXME: the success case isn't documented correctly in librados.h
	if ret == 36 {
		fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
		return fsid, nil
	} else {
		return "", RadosError(int(ret))
	}
}

// GetInstanceID returns a globally unique identifier for the cluster
// connection instance.
func (c *Conn) GetInstanceID() uint64 {
	// FIXME: are there any error cases for this?
	return uint64(C.rados_get_instance_id(c.cluster))
}

// MakePool creates a new pool with default settings.
func (c *Conn) MakePool(name string) error {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_pool_create(c.cluster, c_name))
	if ret == 0 {
		return nil
	} else {
		return RadosError(ret)
	}
}

// DeletePool deletes a pool and all the data inside the pool.
func (c *Conn) DeletePool(name string) error {
	if err := c.ensure_connected(); err != nil {
		fmt.Println("NOT CONNECTED WHOOPS")
		return err
	}
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_pool_delete(c.cluster, c_name))
	if ret == 0 {
		return nil
	} else {
		return RadosError(ret)
	}
}

// GetPoolByName returns the ID of the pool with a given name.
func (c *Conn) GetPoolByName(name string) (int64, error) {
	if err := c.ensure_connected(); err != nil {
		fmt.Println("NOT CONNECTED WHOOPS")
		return 0, err
	}
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int64(C.rados_pool_lookup(c.cluster, c_name))
	if ret < 0 {
		return 0, RadosError(ret)
	} else {
		return ret, nil
	}
}

// GetPoolByID returns the name of a pool by a given ID.
func (c *Conn) GetPoolByID(id int64) (string, error) {
	buf := make([]byte, 4096)
	if err := c.ensure_connected(); err != nil {
		fmt.Println("NOT CONNECTED WHOOPS")
		return "", err
	}
	c_id := C.int64_t(id)
	ret := int(C.rados_pool_reverse_lookup(c.cluster, c_id, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	if ret < 0 {
		return "", RadosError(ret)
	} else {
		return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
	}
}

// MonCommand sends a command to one of the monitors
func (c *Conn) MonCommand(args []byte) (buffer []byte, info string, err error) {
	return c.monCommand(args, nil)
}

// MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) (buffer []byte, info string, err error) {
	return c.monCommand(args, inputBuffer)
}

func (c *Conn) monCommand(args, inputBuffer []byte) (buffer []byte, info string, err error) {
	argv := C.CString(string(args))
	defer C.free(unsafe.Pointer(argv))

	var (
		outs, outbuf       *C.char
		outslen, outbuflen C.size_t
	)
	inbuf := C.CString(string(inputBuffer))
	inbufLen := len(inputBuffer)
	defer C.free(unsafe.Pointer(inbuf))

	ret := C.rados_mon_command(c.cluster,
		&argv, 1,
		inbuf,              // bulk input (e.g. crush map)
		C.size_t(inbufLen), // length inbuf
		&outbuf,            // buffer
		&outbuflen,         // buffer length
		&outs,              // status string
		&outslen)

	if outslen > 0 {
		info = C.GoStringN(outs, C.int(outslen))
		C.free(unsafe.Pointer(outs))
	}
	if outbuflen > 0 {
		buffer = C.GoBytes(unsafe.Pointer(outbuf), C.int(outbuflen))
		C.free(unsafe.Pointer(outbuf))
	}
	if ret != 0 {
		err = RadosError(int(ret))
		return nil, info, err
	}

	return
}
<commit_msg>rados: replace Println calls with a proper error<commit_after>package rados

// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"

import (
	"bytes"
	"errors"
	"unsafe"
)

var (
	ErrNotConnected = errors.New("RADOS not connected")
)

// ClusterStat represents Ceph cluster statistics.
type ClusterStat struct {
	Kb          uint64
	Kb_used     uint64
	Kb_avail    uint64
	Num_objects uint64
}

// Conn is a connection handle to a Ceph cluster.
type Conn struct {
	cluster   C.rados_t
	connected bool
}

// PingMonitor sends a ping to a monitor and returns the reply.
func (c *Conn) PingMonitor(id string) (string, error) {
	c_id := C.CString(id)
	defer C.free(unsafe.Pointer(c_id))

	var strlen C.size_t
	var strout *C.char

	ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
	defer C.rados_buffer_free(strout)

	if ret == 0 {
		reply := C.GoStringN(strout, (C.int)(strlen))
		return reply, nil
	} else {
		return "", RadosError(int(ret))
	}
}

// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
	ret := C.rados_connect(c.cluster)
	if ret != 0 {
		return RadosError(int(ret))
	}
	c.connected = true
	return nil
}

// Shutdown disconnects from the cluster.
func (c *Conn) Shutdown() {
	if err := c.ensure_connected(); err != nil {
		return
	}
	freeConn(c)
}

// ReadConfigFile configures the connection using a Ceph configuration file.
func (c *Conn) ReadConfigFile(path string) error {
	c_path := C.CString(path)
	defer C.free(unsafe.Pointer(c_path))
	ret := C.rados_conf_read_file(c.cluster, c_path)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

// ReadDefaultConfigFile configures the connection using a Ceph configuration
// file located at default locations.
func (c *Conn) ReadDefaultConfigFile() error {
	ret := C.rados_conf_read_file(c.cluster, nil)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
	c_pool := C.CString(pool)
	defer C.free(unsafe.Pointer(c_pool))
	ioctx := &IOContext{}
	ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
	if ret == 0 {
		return ioctx, nil
	} else {
		return nil, RadosError(int(ret))
	}
}

// ListPools returns the names of all existing pools.
func (c *Conn) ListPools() (names []string, err error) {
	buf := make([]byte, 4096)
	for {
		ret := int(C.rados_pool_list(c.cluster,
			(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
		if ret < 0 {
			return nil, RadosError(int(ret))
		}

		if ret > len(buf) {
			buf = make([]byte, ret)
			continue
		}

		tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
		for _, s := range tmp {
			if len(s) > 0 {
				name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
				names = append(names, name)
			}
		}

		return names, nil
	}
}

// SetConfigOption sets the value of the configuration option identified by
// the given name.
func (c *Conn) SetConfigOption(option, value string) error {
	c_opt, c_val := C.CString(option), C.CString(value)
	defer C.free(unsafe.Pointer(c_opt))
	defer C.free(unsafe.Pointer(c_val))
	ret := C.rados_conf_set(c.cluster, c_opt, c_val)
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

// GetConfigOption returns the value of the Ceph configuration option
// identified by the given name.
func (c *Conn) GetConfigOption(name string) (value string, err error) {
	buf := make([]byte, 4096)
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_conf_get(c.cluster, c_name,
		(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	// FIXME: ret may be -ENAMETOOLONG if the buffer is not large enough. We
	// can handle this case, but we need a reliable way to test for
	// -ENAMETOOLONG constant. Will the syscall/Errno stuff in Go help?
	if ret == 0 {
		value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
		return value, nil
	} else {
		return "", RadosError(ret)
	}
}

// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
// retrieved.
func (c *Conn) WaitForLatestOSDMap() error {
	ret := C.rados_wait_for_latest_osdmap(c.cluster)
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

func (c *Conn) ensure_connected() error {
	if c.connected {
		return nil
	} else {
		return ErrNotConnected
	}
}

// GetClusterStats returns statistics about the cluster associated with the
// connection.
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
	if err := c.ensure_connected(); err != nil {
		return ClusterStat{}, err
	}
	c_stat := C.struct_rados_cluster_stat_t{}
	ret := C.rados_cluster_stat(c.cluster, &c_stat)
	if ret < 0 {
		return ClusterStat{}, RadosError(int(ret))
	} else {
		return ClusterStat{
			Kb:          uint64(c_stat.kb),
			Kb_used:     uint64(c_stat.kb_used),
			Kb_avail:    uint64(c_stat.kb_avail),
			Num_objects: uint64(c_stat.num_objects),
		}, nil
	}
}

// ParseCmdLineArgs configures the connection from command line arguments.
func (c *Conn) ParseCmdLineArgs(args []string) error {
	// add an empty element 0 -- Ceph treats the array as the actual contents
	// of argv and skips the first element (the executable name)
	argc := C.int(len(args) + 1)
	argv := make([]*C.char, argc)

	// make the first element a string just in case it is ever examined
	argv[0] = C.CString("placeholder")
	defer C.free(unsafe.Pointer(argv[0]))

	for i, arg := range args {
		argv[i+1] = C.CString(arg)
		defer C.free(unsafe.Pointer(argv[i+1]))
	}

	ret := C.rados_conf_parse_argv(c.cluster, argc, &argv[0])
	if ret < 0 {
		return RadosError(int(ret))
	} else {
		return nil
	}
}

// ParseDefaultConfigEnv configures the connection from the default Ceph
// environment variable(s).
func (c *Conn) ParseDefaultConfigEnv() error {
	ret := C.rados_conf_parse_env(c.cluster, nil)
	if ret == 0 {
		return nil
	} else {
		return RadosError(int(ret))
	}
}

// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
// is a unique identifier of an entire Ceph cluster.
func (c *Conn) GetFSID() (fsid string, err error) {
	buf := make([]byte, 37)
	ret := int(C.rados_cluster_fsid(c.cluster,
		(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	// FIXME: the success case isn't documented correctly in librados.h
	if ret == 36 {
		fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
		return fsid, nil
	} else {
		return "", RadosError(int(ret))
	}
}

// GetInstanceID returns a globally unique identifier for the cluster
// connection instance.
func (c *Conn) GetInstanceID() uint64 {
	// FIXME: are there any error cases for this?
	return uint64(C.rados_get_instance_id(c.cluster))
}

// MakePool creates a new pool with default settings.
func (c *Conn) MakePool(name string) error {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_pool_create(c.cluster, c_name))
	if ret == 0 {
		return nil
	} else {
		return RadosError(ret)
	}
}

// DeletePool deletes a pool and all the data inside the pool.
func (c *Conn) DeletePool(name string) error {
	if err := c.ensure_connected(); err != nil {
		return err
	}
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int(C.rados_pool_delete(c.cluster, c_name))
	if ret == 0 {
		return nil
	} else {
		return RadosError(ret)
	}
}

// GetPoolByName returns the ID of the pool with a given name.
func (c *Conn) GetPoolByName(name string) (int64, error) {
	if err := c.ensure_connected(); err != nil {
		return 0, err
	}
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	ret := int64(C.rados_pool_lookup(c.cluster, c_name))
	if ret < 0 {
		return 0, RadosError(ret)
	} else {
		return ret, nil
	}
}

// GetPoolByID returns the name of a pool by a given ID.
func (c *Conn) GetPoolByID(id int64) (string, error) {
	buf := make([]byte, 4096)
	if err := c.ensure_connected(); err != nil {
		return "", err
	}
	c_id := C.int64_t(id)
	ret := int(C.rados_pool_reverse_lookup(c.cluster, c_id, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
	if ret < 0 {
		return "", RadosError(ret)
	} else {
		return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
	}
}

// MonCommand sends a command to one of the monitors
func (c *Conn) MonCommand(args []byte) (buffer []byte, info string, err error) {
	return c.monCommand(args, nil)
}

// MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) (buffer []byte, info string, err error) {
	return c.monCommand(args, inputBuffer)
}

func (c *Conn) monCommand(args, inputBuffer []byte) (buffer []byte, info string, err error) {
	argv := C.CString(string(args))
	defer C.free(unsafe.Pointer(argv))

	var (
		outs, outbuf       *C.char
		outslen, outbuflen C.size_t
	)
	inbuf := C.CString(string(inputBuffer))
	inbufLen := len(inputBuffer)
	defer C.free(unsafe.Pointer(inbuf))

	ret := C.rados_mon_command(c.cluster,
		&argv, 1,
		inbuf,              // bulk input (e.g. crush map)
		C.size_t(inbufLen), // length inbuf
		&outbuf,            // buffer
		&outbuflen,         // buffer length
		&outs,              // status string
		&outslen)

	if outslen > 0 {
		info = C.GoStringN(outs, C.int(outslen))
		C.free(unsafe.Pointer(outs))
	}
	if outbuflen > 0 {
		buffer = C.GoBytes(unsafe.Pointer(outbuf), C.int(outbuflen))
		C.free(unsafe.Pointer(outbuf))
	}
	if ret != 0 {
		err = RadosError(int(ret))
		return nil, info, err
	}

	return
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 get

import (
	"bytes"
	"regexp"
	"strings"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	api "k8s.io/kubernetes/pkg/apis/core"
)

func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
	testObject := &api.Pod{ObjectMeta: metav1.ObjectMeta{
		Name: "foo",
		Labels: map[string]string{
			"l1": "value",
		},
	}}

	testCases := []struct {
		name       string
		showKind   bool
		showLabels bool

		// TODO(juanvallejo): test sorting once it's moved to the HumanReadablePrinter
		sortBy       string
		columnLabels []string

		noHeaders     bool
		withNamespace bool

		outputFormat string

		expectedError  string
		expectedOutput string
		expectNoMatch  bool
	}{
		{
			name:           "empty output format matches a humanreadable printer",
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\nfoo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "\"wide\" output format prints",
			outputFormat:   "wide",
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\\ +READINESS GATES\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "no-headers prints output with no headers",
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "no-headers and a \"wide\" output format prints output with no headers and additional columns",
			outputFormat:   "wide",
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "show-kind displays the resource's kind, even when printing a single type of resource",
			showKind:       true,
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "label-columns prints specified label values in new column",
			columnLabels:   []string{"l1"},
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +value\n",
		},
		{
			name:           "withNamespace displays an additional NAMESPACE column",
			withNamespace:  true,
			expectedOutput: "NAMESPACE\\ +NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\n\\ +foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:          "no printer is matched on an invalid outputFormat",
			outputFormat:  "invalid",
			expectNoMatch: true,
		},
		{
			name:          "printer should not match on any other format supported by another printer",
			outputFormat:  "go-template",
			expectNoMatch: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			printFlags := HumanPrintFlags{
				ShowKind:     &tc.showKind,
				ShowLabels:   &tc.showLabels,
				SortBy:       &tc.sortBy,
				ColumnLabels: &tc.columnLabels,

				NoHeaders:     tc.noHeaders,
				WithNamespace: tc.withNamespace,
			}

			if tc.showKind {
				printFlags.Kind = schema.GroupKind{Kind: "pod"}
			}

			p, err := printFlags.ToPrinter(tc.outputFormat)
			if tc.expectNoMatch {
				if !genericclioptions.IsNoCompatiblePrinterError(err) {
					t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
				}
				return
			}
			if genericclioptions.IsNoCompatiblePrinterError(err) {
				t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
			}

			if len(tc.expectedError) > 0 {
				if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
					t.Errorf("expecting error %q, got %v", tc.expectedError, err)
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			out := bytes.NewBuffer([]byte{})
			err = p.PrintObj(testObject, out)
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}

			match, err := regexp.Match(tc.expectedOutput, out.Bytes())
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if !match {
				t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
			}
		})
	}
}
<commit_msg>Test humanreadable table output<commit_after>/*
Copyright 2018 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 get

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

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	api "k8s.io/kubernetes/pkg/apis/core"
)

func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
	testTable := &metav1.Table{
		ColumnDefinitions: []metav1.TableColumnDefinition{
			{Name: "Name", Type: "string", Format: "name"},
			{Name: "Ready", Type: "string", Format: ""},
			{Name: "Status", Type: "string", Format: ""},
			{Name: "Restarts", Type: "integer", Format: ""},
			{Name: "Age", Type: "string", Format: ""},
			{Name: "IP", Type: "string", Format: "", Priority: 1},
			{Name: "Node", Type: "string", Format: "", Priority: 1},
			{Name: "Nominated Node", Type: "string", Format: "", Priority: 1},
			{Name: "Readiness Gates", Type: "string", Format: "", Priority: 1},
		},
		Rows: []metav1.TableRow{{
			Object: runtime.RawExtension{
				Object: &corev1.Pod{
					TypeMeta:   metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
					ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"l1": "value"}},
				},
			},
			Cells: []interface{}{"foo", "0/0", "", int64(0), "<unknown>", "<none>", "<none>", "<none>", "<none>"},
		}},
	}
	testPod := &api.Pod{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
			Labels: map[string]string{
				"l1": "value",
			},
		},
	}

	testCases := []struct {
		name       string
		testObject runtime.Object
		showKind   bool
		showLabels bool

		// TODO(juanvallejo): test sorting once it's moved to the HumanReadablePrinter
		sortBy       string
		columnLabels []string

		noHeaders     bool
		withNamespace bool

		outputFormat string

		expectedError  string
		expectedOutput string
		expectNoMatch  bool
	}{
		{
			name:           "empty output format matches a humanreadable printer",
			testObject:     testPod.DeepCopy(),
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\nfoo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "empty output format matches a humanreadable printer",
			testObject:     testTable.DeepCopy(),
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\nfoo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "\"wide\" output format prints",
			testObject:     testPod.DeepCopy(),
			outputFormat:   "wide",
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\\ +READINESS GATES\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "\"wide\" output format prints",
			testObject:     testTable.DeepCopy(),
			outputFormat:   "wide",
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\\ +READINESS GATES\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "no-headers prints output with no headers",
			testObject:     testPod.DeepCopy(),
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "no-headers prints output with no headers",
			testObject:     testTable.DeepCopy(),
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "no-headers and a \"wide\" output format prints output with no headers and additional columns",
			testObject:     testPod.DeepCopy(),
			outputFormat:   "wide",
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "no-headers and a \"wide\" output format prints output with no headers and additional columns",
			testObject:     testTable.DeepCopy(),
			outputFormat:   "wide",
			noHeaders:      true,
			expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
		},
		{
			name:           "show-kind displays the resource's kind, even when printing a single type of resource",
			testObject:     testPod.DeepCopy(),
			showKind:       true,
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "show-kind displays the resource's kind, even when printing a single type of resource",
			testObject:     testTable.DeepCopy(),
			showKind:       true,
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "label-columns prints specified label values in new column",
			testObject:     testPod.DeepCopy(),
			columnLabels:   []string{"l1"},
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +value\n",
		},
		{
			name:           "label-columns prints specified label values in new column",
			testObject:     testTable.DeepCopy(),
			columnLabels:   []string{"l1"},
			expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +value\n",
		},
		{
			name:           "withNamespace displays an additional NAMESPACE column",
			testObject:     testPod.DeepCopy(),
			withNamespace:  true,
			expectedOutput: "NAMESPACE\\ +NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\n\\ +foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:           "withNamespace displays an additional NAMESPACE column",
			testObject:     testTable.DeepCopy(),
			withNamespace:  true,
			expectedOutput: "NAMESPACE\\ +NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\n\\ +foo\\ +0/0\\ +0\\ +<unknown>\n",
		},
		{
			name:          "no printer is matched on an invalid outputFormat",
			testObject:    testPod.DeepCopy(),
			outputFormat:  "invalid",
			expectNoMatch: true,
		},
		{
			name:          "printer should not match on any other format supported by another printer",
			testObject:    testPod.DeepCopy(),
			outputFormat:  "go-template",
			expectNoMatch: true,
		},
	}

	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s %T", tc.name, tc.testObject), func(t *testing.T) {
			printFlags := HumanPrintFlags{
				ShowKind:     &tc.showKind,
				ShowLabels:   &tc.showLabels,
				SortBy:       &tc.sortBy,
				ColumnLabels: &tc.columnLabels,

				NoHeaders:     tc.noHeaders,
				WithNamespace: tc.withNamespace,
			}

			if tc.showKind {
				printFlags.Kind = schema.GroupKind{Kind: "pod"}
			}

			p, err := printFlags.ToPrinter(tc.outputFormat)
			if tc.expectNoMatch {
				if !genericclioptions.IsNoCompatiblePrinterError(err) {
					t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
				}
				return
			}
			if genericclioptions.IsNoCompatiblePrinterError(err) {
				t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
			}

			if len(tc.expectedError) > 0 {
				if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
					t.Errorf("expecting error %q, got %v", tc.expectedError, err)
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			out := bytes.NewBuffer([]byte{})
			err = p.PrintObj(tc.testObject, out)
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}

			match, err := regexp.Match(tc.expectedOutput, out.Bytes())
			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if !match {
				t.Errorf("unexpected output: expecting\n%s\ngot\n%s", tc.expectedOutput, out.String())
			}
		})
	}
}
<|endoftext|>


<commit_before>package turnservicecli

import (
	"sync"
	"time"
)

// CachedCredentialsData combine CredentialsData with a expiration timer.
type CachedCredentialsData struct {
	sync.RWMutex

	Turn    *CredentialsData
	expires int64
	expired bool

	closed bool
	quit   chan bool
}

// NewCachedCredentialsData add expiration timer with a percentile to CredentialsData.
func NewCachedCredentialsData(turn *CredentialsData, expirationPercentile uint) *CachedCredentialsData {
	c := &CachedCredentialsData{
		Turn:    turn,
		expires: time.Now().Unix() + turn.TTL,
		quit:    make(chan bool),
	}

	go func() {
		expiry := turn.TTL * int64(expirationPercentile) / 100
		select {
		case <-c.quit:
		case <-time.After(time.Duration(expiry) * time.Second):
		}
		c.Lock()
		defer c.Unlock()
		c.expired = true
	}()

	return c
}

// Expired returns if the cached CredentialsData has expired.
func (c *CachedCredentialsData) Expired() bool {
	c.RLock()
	defer c.RUnlock()
	return c.expired || c.closed
}

// TTL returns the remaining TTL of the cached CredentialsData.
func (c *CachedCredentialsData) TTL() int64 {
	ttl := c.expires - time.Now().Unix()
	if ttl < 0 {
		return 0
	}
	return ttl
}

// Close closes the cached CredentialsData and expires it if not already expired.
func (c *CachedCredentialsData) Close() {
	c.Lock()
	defer c.Unlock()
	if !c.expired {
		close(c.quit)
	}
	c.closed = true
}
<commit_msg>Avoid panic on multiple Close() calls<commit_after>package turnservicecli

import (
	"sync"
	"time"
)

// CachedCredentialsData combine CredentialsData with a expiration timer.
type CachedCredentialsData struct {
	sync.RWMutex

	Turn    *CredentialsData
	expires int64
	expired bool

	closed bool
	quit   chan bool
}

// NewCachedCredentialsData add expiration timer with a percentile to CredentialsData.
func NewCachedCredentialsData(turn *CredentialsData, expirationPercentile uint) *CachedCredentialsData {
	c := &CachedCredentialsData{
		Turn:    turn,
		expires: time.Now().Unix() + turn.TTL,
		quit:    make(chan bool),
	}

	go func() {
		expiry := turn.TTL * int64(expirationPercentile) / 100
		select {
		case <-c.quit:
		case <-time.After(time.Duration(expiry) * time.Second):
		}
		c.Lock()
		defer c.Unlock()
		c.expired = true
	}()

	return c
}

// Expired returns if the cached CredentialsData has expired.
func (c *CachedCredentialsData) Expired() bool {
	c.RLock()
	defer c.RUnlock()
	return c.expired || c.closed
}

// TTL returns the remaining TTL of the cached CredentialsData.
func (c *CachedCredentialsData) TTL() int64 {
	ttl := c.expires - time.Now().Unix()
	if ttl < 0 {
		return 0
	}
	return ttl
}

// Close closes the cached CredentialsData and expires it if not already expired.
func (c *CachedCredentialsData) Close() {
	c.Lock()
	defer c.Unlock()
	if !c.expired && !c.closed {
		close(c.quit)
	}
	c.closed = true
}
<|endoftext|>


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

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

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

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

package plugin

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os/exec"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"
	cpmtypes "k8s.io/node-problem-detector/pkg/custompluginmonitor/types"
	"k8s.io/node-problem-detector/pkg/util/tomb"
)

// maxCustomPluginBufferBytes is the max bytes that a custom plugin is allowed to
// send to stdout/stderr. Any bytes exceeding this value will be truncated.
const maxCustomPluginBufferBytes = 1024 * 4

type Plugin struct {
	config     cpmtypes.CustomPluginConfig
	syncChan   chan struct{}
	resultChan chan cpmtypes.Result
	tomb       *tomb.Tomb
	sync.WaitGroup
}

func NewPlugin(config cpmtypes.CustomPluginConfig) *Plugin {
	return &Plugin{
		config:   config,
		syncChan: make(chan struct{}, *config.PluginGlobalConfig.Concurrency),
		// A 1000 size channel should be big enough.
		resultChan: make(chan cpmtypes.Result, 1000),
		tomb:       tomb.NewTomb(),
	}
}

func (p *Plugin) GetResultChan() <-chan cpmtypes.Result {
	return p.resultChan
}

func (p *Plugin) Run() {
	defer func() {
		glog.Info("Stopping plugin execution")
		close(p.resultChan)
		p.tomb.Done()
	}()

	runTicker := time.NewTicker(*p.config.PluginGlobalConfig.InvokeInterval)
	defer runTicker.Stop()

	// on boot run once
	select {
	case <-p.tomb.Stopping():
		return
	default:
		p.runRules()
	}

	// run every InvokeInterval
	for {
		select {
		case <-runTicker.C:
			p.runRules()
		case <-p.tomb.Stopping():
			return
		}
	}
}

// run each rule in parallel and wait for them to complete
func (p *Plugin) runRules() {
	glog.Info("Start to run custom plugins")

	for _, rule := range p.config.Rules {
		p.syncChan <- struct{}{}
		p.Add(1)
		go func(rule *cpmtypes.CustomRule) {
			defer p.Done()
			defer func() {
				<-p.syncChan
			}()

			start := time.Now()
			exitStatus, message := p.run(*rule)

			glog.V(3).Infof("Rule: %+v. Start time: %v. End time: %v. Duration: %v", rule, start, time.Now(), time.Since(start))

			result := cpmtypes.Result{
				Rule:       rule,
				ExitStatus: exitStatus,
				Message:    message,
			}

			p.resultChan <- result

			// Let the result be logged at a higher verbosity level. If there is a change in status it is logged later.
			glog.V(3).Infof("Add check result %+v for rule %+v", result, rule)
			glog.Infof("Ran rule %+v", rule)
		}(rule)
	}

	p.Wait()
	glog.Info("Finish running custom plugins")
}

// readFromReader reads the maxBytes from the reader and drains the rest.
func readFromReader(reader io.ReadCloser, maxBytes int64) ([]byte, error) {
	limitReader := io.LimitReader(reader, maxBytes)
	data, err := ioutil.ReadAll(limitReader)
	if err != nil {
		return []byte{}, err
	}
	// Drain the reader
	if _, err := io.Copy(ioutil.Discard, reader); err != nil {
		return []byte{}, err
	}
	return data, nil
}

func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, output string) {
	var ctx context.Context
	var cancel context.CancelFunc

	if rule.Timeout != nil && *rule.Timeout < *p.config.PluginGlobalConfig.Timeout {
		ctx, cancel = context.WithTimeout(context.Background(), *rule.Timeout)
	} else {
		ctx, cancel = context.WithTimeout(context.Background(), *p.config.PluginGlobalConfig.Timeout)
	}
	defer cancel()

	cmd := exec.CommandContext(ctx, rule.Path, rule.Args...)

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		glog.Errorf("Error creating stdout pipe for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error creating stdout pipe for plugin. Please check the error log"
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		glog.Errorf("Error creating stderr pipe for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error creating stderr pipe for plugin. Please check the error log"
	}
	if err := cmd.Start(); err != nil {
		glog.Errorf("Error in starting plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error in starting plugin. Please check the error log"
	}

	var (
		wg        sync.WaitGroup
		stdout    []byte
		stderr    []byte
		stdoutErr error
		stderrErr error
	)

	wg.Add(2)
	go func() {
		stdout, stdoutErr = readFromReader(stdoutPipe, maxCustomPluginBufferBytes)
		wg.Done()
	}()
	go func() {
		stderr, stderrErr = readFromReader(stderrPipe, maxCustomPluginBufferBytes)
		wg.Done()
	}()
	// This will wait for the reads to complete. If the execution times out, the pipes
	// will be closed and the wait group unblocks.
	wg.Wait()

	if stdoutErr != nil {
		glog.Errorf("Error reading stdout for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error reading stdout for plugin. Please check the error log"
	}

	if stderrErr != nil {
		glog.Errorf("Error reading stderr for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error reading stderr for plugin. Please check the error log"
	}

	if err := cmd.Wait(); err != nil {
		if _, ok := err.(*exec.ExitError); !ok {
			glog.Errorf("Error in waiting for plugin %q: error - %v. output - %q", rule.Path, err, string(stdout))
			return cpmtypes.Unknown, "Error in waiting for plugin. Please check the error log"
		}
	}

	// log the stderr from the plugin
	if len(stderr) != 0 {
		glog.Infof("Start logs from plugin %q \n %s", rule.Path, string(stderr))
		glog.Infof("End logs from plugin %q", rule.Path)
	}

	// trim suffix useless bytes
	output = string(stdout)
	output = strings.TrimSpace(output)

	if cmd.ProcessState.Sys().(syscall.WaitStatus).Signaled() {
		output = fmt.Sprintf("Timeout when running plugin %q: state - %s. output - %q", rule.Path, cmd.ProcessState.String(), output)
	}

	// cut at position max_output_length if stdout is longer than max_output_length bytes
	if len(output) > *p.config.PluginGlobalConfig.MaxOutputLength {
		output = output[:*p.config.PluginGlobalConfig.MaxOutputLength]
	}

	exitCode := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
	switch exitCode {
	case 0:
		return cpmtypes.OK, output
	case 1:
		return cpmtypes.NonOK, output
	default:
		return cpmtypes.Unknown, output
	}
}

func (p *Plugin) Stop() {
	p.tomb.Stop()
	glog.Info("Stop plugin execution")
}
<commit_msg>fix: fix script timeout can't work<commit_after>/*
Copyright 2017 The Kubernetes Authors All rights reserved.

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

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

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

package plugin

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os/exec"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"
	cpmtypes "k8s.io/node-problem-detector/pkg/custompluginmonitor/types"
	"k8s.io/node-problem-detector/pkg/util/tomb"
)

// maxCustomPluginBufferBytes is the max bytes that a custom plugin is allowed to
// send to stdout/stderr. Any bytes exceeding this value will be truncated.
const maxCustomPluginBufferBytes = 1024 * 4

type Plugin struct {
	config     cpmtypes.CustomPluginConfig
	syncChan   chan struct{}
	resultChan chan cpmtypes.Result
	tomb       *tomb.Tomb
	sync.WaitGroup
}

func NewPlugin(config cpmtypes.CustomPluginConfig) *Plugin {
	return &Plugin{
		config:   config,
		syncChan: make(chan struct{}, *config.PluginGlobalConfig.Concurrency),
		// A 1000 size channel should be big enough.
		resultChan: make(chan cpmtypes.Result, 1000),
		tomb:       tomb.NewTomb(),
	}
}

func (p *Plugin) GetResultChan() <-chan cpmtypes.Result {
	return p.resultChan
}

func (p *Plugin) Run() {
	defer func() {
		glog.Info("Stopping plugin execution")
		close(p.resultChan)
		p.tomb.Done()
	}()

	runTicker := time.NewTicker(*p.config.PluginGlobalConfig.InvokeInterval)
	defer runTicker.Stop()

	// on boot run once
	select {
	case <-p.tomb.Stopping():
		return
	default:
		p.runRules()
	}

	// run every InvokeInterval
	for {
		select {
		case <-runTicker.C:
			p.runRules()
		case <-p.tomb.Stopping():
			return
		}
	}
}

// run each rule in parallel and wait for them to complete
func (p *Plugin) runRules() {
	glog.Info("Start to run custom plugins")

	for _, rule := range p.config.Rules {
		p.syncChan <- struct{}{}
		p.Add(1)
		go func(rule *cpmtypes.CustomRule) {
			defer p.Done()
			defer func() {
				<-p.syncChan
			}()

			start := time.Now()
			exitStatus, message := p.run(*rule)

			glog.V(3).Infof("Rule: %+v. Start time: %v. End time: %v. Duration: %v", rule, start, time.Now(), time.Since(start))

			result := cpmtypes.Result{
				Rule:       rule,
				ExitStatus: exitStatus,
				Message:    message,
			}

			p.resultChan <- result

			// Let the result be logged at a higher verbosity level. If there is a change in status it is logged later.
			glog.V(3).Infof("Add check result %+v for rule %+v", result, rule)
			glog.Infof("Ran rule %+v", rule)
		}(rule)
	}

	p.Wait()
	glog.Info("Finish running custom plugins")
}

// readFromReader reads the maxBytes from the reader and drains the rest.
func readFromReader(reader io.ReadCloser, maxBytes int64) ([]byte, error) {
	limitReader := io.LimitReader(reader, maxBytes)
	data, err := ioutil.ReadAll(limitReader)
	if err != nil {
		return []byte{}, err
	}
	// Drain the reader
	if _, err := io.Copy(ioutil.Discard, reader); err != nil {
		return []byte{}, err
	}
	return data, nil
}

func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, output string) {
	var ctx context.Context
	var cancel context.CancelFunc

	if rule.Timeout != nil && *rule.Timeout < *p.config.PluginGlobalConfig.Timeout {
		ctx, cancel = context.WithTimeout(context.Background(), *rule.Timeout)
	} else {
		ctx, cancel = context.WithTimeout(context.Background(), *p.config.PluginGlobalConfig.Timeout)
	}
	defer cancel()

	// create a process group
	sysProcAttr := &syscall.SysProcAttr{
		Setpgid: true,
	}
	cmd := exec.Command(rule.Path, rule.Args...)
	cmd.SysProcAttr = sysProcAttr

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		glog.Errorf("Error creating stdout pipe for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error creating stdout pipe for plugin. Please check the error log"
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		glog.Errorf("Error creating stderr pipe for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error creating stderr pipe for plugin. Please check the error log"
	}
	if err := cmd.Start(); err != nil {
		glog.Errorf("Error in starting plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error in starting plugin. Please check the error log"
	}

	waitChan := make(chan struct{})
	defer close(waitChan)

	go func() {
		select {
		case <-ctx.Done():
			glog.Errorf("Error in running plugin timeout %q", rule.Path)
			if cmd.Process == nil || cmd.Process.Pid == 0 {
				glog.Errorf("Error in cmd.Process check %q", rule.Path)
				break
			}
			err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
			if err != nil {
				glog.Errorf("Error in kill process %d, %v", cmd.Process.Pid, err)
			}
		case <-waitChan:
			return
		}
	}()

	var (
		wg        sync.WaitGroup
		stdout    []byte
		stderr    []byte
		stdoutErr error
		stderrErr error
	)

	wg.Add(2)
	go func() {
		stdout, stdoutErr = readFromReader(stdoutPipe, maxCustomPluginBufferBytes)
		wg.Done()
	}()
	go func() {
		stderr, stderrErr = readFromReader(stderrPipe, maxCustomPluginBufferBytes)
		wg.Done()
	}()
	// This will wait for the reads to complete. If the execution times out, the pipes
	// will be closed and the wait group unblocks.
	wg.Wait()

	if stdoutErr != nil {
		glog.Errorf("Error reading stdout for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error reading stdout for plugin. Please check the error log"
	}

	if stderrErr != nil {
		glog.Errorf("Error reading stderr for plugin %q: error - %v", rule.Path, err)
		return cpmtypes.Unknown, "Error reading stderr for plugin. Please check the error log"
	}

	if err := cmd.Wait(); err != nil {
		if _, ok := err.(*exec.ExitError); !ok {
			glog.Errorf("Error in waiting for plugin %q: error - %v. output - %q", rule.Path, err, string(stdout))
			return cpmtypes.Unknown, "Error in waiting for plugin. Please check the error log"
		}
	}

	// log the stderr from the plugin
	if len(stderr) != 0 {
		glog.Infof("Start logs from plugin %q \n %s", rule.Path, string(stderr))
		glog.Infof("End logs from plugin %q", rule.Path)
	}

	// trim suffix useless bytes
	output = string(stdout)
	output = strings.TrimSpace(output)

	if cmd.ProcessState.Sys().(syscall.WaitStatus).Signaled() {
		output = fmt.Sprintf("Timeout when running plugin %q: state - %s. output - %q", rule.Path, cmd.ProcessState.String(), output)
	}

	// cut at position max_output_length if stdout is longer than max_output_length bytes
	if len(output) > *p.config.PluginGlobalConfig.MaxOutputLength {
		output = output[:*p.config.PluginGlobalConfig.MaxOutputLength]
	}

	exitCode := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
	switch exitCode {
	case 0:
		return cpmtypes.OK, output
	case 1:
		return cpmtypes.NonOK, output
	default:
		return cpmtypes.Unknown, output
	}
}

func (p *Plugin) Stop() {
	p.tomb.Stop()
	glog.Info("Stop plugin execution")
}
<|endoftext|>


<commit_before>package runtime

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/ta2gch/iris/runtime/env"
	"github.com/ta2gch/iris/runtime/ilos"
	"github.com/ta2gch/iris/runtime/ilos/class"
	"github.com/ta2gch/iris/runtime/ilos/instance"
)

func FormatObject(e env.Environment, stream, object, escapep ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if escapep == T {
		fmt.Fprint(stream.(instance.Stream).Writer, object)
		return Nil, nil
	}
	if ok, _ := Stringp(e, object); ok == T {
		fmt.Fprint(stream.(instance.Stream).Writer, string(object.(instance.String)))
		return Nil, nil
	}
	if ok, _ := Characterp(e, object); ok == T {
		fmt.Fprint(stream.(instance.Stream).Writer, string(object.(instance.Character)))
		return Nil, nil
	}
	fmt.Fprint(stream.(instance.Stream).Writer, object)
	return Nil, nil
}

func FormatChar(e env.Environment, stream, object ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Characterp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Character), Nil)
	}
	fmt.Fprint(stream.(instance.Stream).Writer, rune(object.(instance.Character)))
	return Nil, nil
}

func FormatFloat(e env.Environment, stream, object ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Floatp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Float), Nil)
	}
	fmt.Fprint(stream.(instance.Stream).Writer, float64(object.(instance.Float)))
	return Nil, nil
}

func FormatInteger(e env.Environment, stream, object, radix ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Integerp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Integer), Nil)
	}
	if ok, err := Integerp(e, radix); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, radix, class.Integer), Nil)
	}
	i := int(object.(instance.Integer))
	r := int(radix.(instance.Integer))
	fmt.Fprint(stream.(instance.Stream).Writer, strconv.FormatInt(int64(i), r))
	return Nil, nil
}

func FormatTab(e env.Environment, stream, num ilos.Instance) (ilos.Instance, ilos.Instance) {
	return FormatChar(e, stream, instance.NewCharacter(' '))
}

func FormatFreshLine(e env.Environment, stream ilos.Instance) (ilos.Instance, ilos.Instance) {
	return FormatChar(e, stream, instance.NewCharacter(rune("\n"[0])))
}

func Format(env env.Environment, stream, formatString ilos.Instance, formatArguments ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	if ok, _ := Stringp(env, formatString); ok == Nil {
		return SignalCondition(env, instance.NewDomainError(env, formatString, class.String), Nil)
	}
	str := string(formatString.(instance.String))
	re := regexp.MustCompile(`~(?:[ABCDGOSX%&~]|[0-9]+[RT])`)
	s, e, c := 0, 0, 0
	for loc := re.FindStringIndex(str[s:]); loc != nil; loc = re.FindStringIndex(str[s:]) {
		if c == len(formatArguments) {
			return SignalCondition(env, instance.NewArityError(env), Nil)
		}
		b := s
		s = b + 0
		e = b + loc[0]
		FormatObject(env, stream, instance.NewString([]rune(str[s:e])), Nil)
		s = b + loc[0]
		e = b + loc[1]
		if str[s:e] == "~A" {
			if _, err := FormatObject(env, stream, formatArguments[c], Nil); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~B" {
			if _, err := FormatInteger(env, stream, formatArguments[c], instance.NewInteger(2)); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~C" {
			if _, err := FormatChar(env, stream, formatArguments[c]); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~D" {
			if _, err := FormatInteger(env, stream, formatArguments[c], instance.NewInteger(10)); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~G" {
			if _, err := FormatFloat(env, stream, formatArguments[c]); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~O" {
			if _, err := FormatInteger(env, stream, formatArguments[c], instance.NewInteger(8)); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~S" {
			if _, err := FormatObject(env, stream, formatArguments[c], T); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~X" {
			if _, err := FormatInteger(env, stream, formatArguments[c], instance.NewInteger(16)); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[s:e] == "~%" {
			if _, err := FormatChar(env, stream, instance.NewCharacter(rune("\n"[0]))); err != nil {
				return nil, err
			}
			s = e
			continue
		}
		if str[s:e] == "~&" {
			if _, err := FormatFreshLine(env, stream); err != nil {
				return nil, err
			}
			s = e
			continue
		}
		if str[s:e] == "~~" {
			if _, err := FormatChar(env, stream, instance.NewCharacter('~')); err != nil {
				return nil, err
			}
			s = e
			continue
		}
		if str[len(str)-1] == 'R' {
			n, _ := strconv.Atoi(str[1 : len(str)-1])
			if _, err := FormatInteger(env, stream, formatArguments[c], instance.NewInteger(n)); err != nil {
				return nil, err
			}
			c++
			s = e
			continue
		}
		if str[len(str)-1] == 'T' {
			n, _ := strconv.Atoi(str[1 : len(str)-1])
			if _, err := FormatTab(env, stream, instance.NewInteger(n)); err != nil {
				return nil, err
			}
			s = e
			continue
		}
	}
	fmt.Print(str[s:])
	return Nil, nil
}
<commit_msg>Refactor Format<commit_after>package runtime

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/ta2gch/iris/runtime/env"
	"github.com/ta2gch/iris/runtime/ilos"
	"github.com/ta2gch/iris/runtime/ilos/class"
	"github.com/ta2gch/iris/runtime/ilos/instance"
)

func FormatObject(e env.Environment, stream, object, escapep ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if escapep == T {
		fmt.Fprint(stream.(instance.Stream).Writer, object)
		return Nil, nil
	}
	if ok, _ := Stringp(e, object); ok == T {
		fmt.Fprint(stream.(instance.Stream).Writer, string(object.(instance.String)))
		return Nil, nil
	}
	if ok, _ := Characterp(e, object); ok == T {
		fmt.Fprint(stream.(instance.Stream).Writer, string(object.(instance.Character)))
		return Nil, nil
	}
	fmt.Fprint(stream.(instance.Stream).Writer, object)
	return Nil, nil
}

func FormatChar(e env.Environment, stream, object ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Characterp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Character), Nil)
	}
	fmt.Fprint(stream.(instance.Stream).Writer, rune(object.(instance.Character)))
	return Nil, nil
}

func FormatFloat(e env.Environment, stream, object ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Floatp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Float), Nil)
	}
	fmt.Fprint(stream.(instance.Stream).Writer, float64(object.(instance.Float)))
	return Nil, nil
}

func FormatInteger(e env.Environment, stream, object, radix ilos.Instance) (ilos.Instance, ilos.Instance) {
	ok, err := OpenStreamP(e, stream)
	if err != nil || ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, stream, class.Stream), Nil)
	}
	if ok, err := Integerp(e, object); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, object, class.Integer), Nil)
	}
	if ok, err := Integerp(e, radix); ok == Nil || err != nil {
		return SignalCondition(e, instance.NewDomainError(e, radix, class.Integer), Nil)
	}
	i := int(object.(instance.Integer))
	r := int(radix.(instance.Integer))
	fmt.Fprint(stream.(instance.Stream).Writer, strconv.FormatInt(int64(i), r))
	return Nil, nil
}

func FormatTab(e env.Environment, stream, num ilos.Instance) (ilos.Instance, ilos.Instance) {
	return FormatChar(e, stream, instance.NewCharacter(' '))
}

func FormatFreshLine(e env.Environment, stream ilos.Instance) (ilos.Instance, ilos.Instance) {
	return FormatChar(e, stream, instance.NewCharacter(rune("\n"[0])))
}

func Format(e env.Environment, stream, formatString ilos.Instance, formatArguments ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	if ok, _ := Stringp(e, formatString); ok == Nil {
		return SignalCondition(e, instance.NewDomainError(e, formatString, class.String), Nil)
	}
	str := string(formatString.(instance.String))
	re := regexp.MustCompile(`~(?:[ABCDGOSX%&~]|[0-9]+[RT])`)
	start, end, index := 0, 0, 0
	for loc := re.FindStringIndex(str[start:]); loc != nil; loc = re.FindStringIndex(str[start:]) {
		if index == len(formatArguments) {
			return SignalCondition(e, instance.NewArityError(e), Nil)
		}
		base := start
		start = base + 0
		end = base + loc[0]
		FormatObject(e, stream, instance.NewString([]rune(str[start:end])), Nil)
		start = base + loc[0]
		end = base + loc[1]
		var err ilos.Instance
		switch str[start:end] {
		case "~A":
			_, err = FormatObject(e, stream, formatArguments[index], Nil)
			index++
		case "~B":
			_, err = FormatInteger(e, stream, formatArguments[index], instance.NewInteger(2))
			index++
		case "~C":
			_, err = FormatChar(e, stream, formatArguments[index])
			index++
		case "~D":
			_, err = FormatInteger(e, stream, formatArguments[index], instance.NewInteger(10))
			index++
		case "~G":
			_, err = FormatFloat(e, stream, formatArguments[index])
			index++
		case "~O":
			_, err = FormatInteger(e, stream, formatArguments[index], instance.NewInteger(8))
			index++
		case "~S":
			_, err = FormatObject(e, stream, formatArguments[index], T)
			index++
		case "~X":
			_, err = FormatInteger(e, stream, formatArguments[index], instance.NewInteger(16))
			index++
		case "~%":
			_, err = FormatChar(e, stream, instance.NewCharacter(rune("\n"[0])))
		case "~&":
			_, err = FormatFreshLine(e, stream)
		case "~~":
			_, err = FormatChar(e, stream, instance.NewCharacter('~'))
		default:
			if str[len(str)-1] == 'R' {
				n, _ := strconv.Atoi(str[1 : len(str)-1])
				_, err = FormatInteger(e, stream, formatArguments[index], instance.NewInteger(n))
				index++
			}
			if str[len(str)-1] == 'T' {
				n, _ := strconv.Atoi(str[1 : len(str)-1])
				_, err = FormatTab(e, stream, instance.NewInteger(n))
			}
		}
		if err != nil {
			return nil, err
		}
		start = end
	}
	fmt.Print(str[start:])
	return Nil, nil
}
<|endoftext|>


<commit_before>package runtime

import (
	"bytes"
	"errors"
	"fmt"
	"math"
)

var (
	ErrValNotAnObject = errors.New("value is not an object")
)

type funcVM struct {
	proto *GoblinFunc
	pc    int
	vars  map[string]Val
	stack []Val
	sp    int
	this  Val
	args  []Val
}

func newFuncVM(proto *GoblinFunc) *funcVM {
	return &funcVM{
		proto,
		0,
		make(map[string]Val, proto.expVars),
		make([]Val, 0, proto.stackSz),
		0,
		nil,
		nil,
	}
}

func (ø *funcVM) push(v Val) {
	// Stack has to grow as needed, StackSz doesn't take into account the loops
	if ø.sp == len(ø.stack) {
		if ø.sp == cap(ø.stack) {
			fmt.Printf("DEBUG expanding stack of func %s, current size: %d\n", ø.proto.name, len(ø.stack))
		}
		ø.stack = append(ø.stack, v)
	} else {
		ø.stack[ø.sp] = v
	}
	ø.sp++
}

func (ø *funcVM) pop() Val {
	ø.sp--
	v := ø.stack[ø.sp]
	ø.stack[ø.sp] = Nil // free this reference for gc
	return v
}

func (ø *funcVM) getVal(flg Flag, ix uint64) Val {
	switch flg {
	case FLG_K:
		return ø.proto.kTable[ix]
	case FLG_V:
		// If not found, will return Nil, so the value is always fine
		v, _ := ø.proto.ctx.getVar(ø.proto.kTable[ix].String())
		return v
	case FLG_N:
		return Nil
	case FLG_T:
		return ø.this
	case FLG_F:
		return ø.proto.ctx.Protos[ix]
	case FLG_AA:
		return ø.args[ix]
	}
	panic(fmt.Sprintf("Func.getVal() - invalid flag value %d", flg))
}

func (ø *funcVM) dump() string {
	buf := bytes.NewBuffer(nil)
	fmt.Fprintf(buf, "\n> %s\n", ø.proto.dump())
	// Constants
	fmt.Fprintf(buf, "  Constants:\n")
	for i, v := range ø.proto.kTable {
		fmt.Fprintf(buf, "    [%3d] %s\n", i, v.dump())
	}
	// Variables
	fmt.Fprintf(buf, "\n  Variables:\n")
	for k, v := range ø.vars {
		fmt.Fprintf(buf, "    %s = %s\n", k, v.dump())
	}
	// Stack
	fmt.Fprintf(buf, "\n  Stack:\n")
	i := int(math.Max(0, float64(ø.sp-5)))
	for i <= ø.sp {
		if i == ø.sp {
			fmt.Fprint(buf, "sp->")
		} else {
			fmt.Fprint(buf, "    ")
		}
		v := Val(Nil)
		if i < len(ø.stack) {
			v = ø.stack[i]
		}
		fmt.Fprintf(buf, "[%3d] %s\n", i, v.dump())
		i++
	}
	// Instructions
	fmt.Fprintf(buf, "\n  Instructions:\n")
	i = int(math.Max(0, float64(ø.pc-3)))
	for i <= ø.pc+3 {
		if i == ø.pc {
			fmt.Fprintf(buf, "pc->")
		} else {
			fmt.Fprintf(buf, "    ")
		}
		if i < len(ø.proto.code) {
			fmt.Fprintf(buf, "[%3d] %s\n", i, ø.proto.code[i])
		} else {
			break
		}
		i++
	}
	fmt.Fprintln(buf)
	return buf.String()
}

func (ø *funcVM) run(args ...Val) Val {
	// Expected args are defined in constant table spots 0 to ExpArgs - 1.
	for j, l := 0, len(args); j < ø.proto.expArgs; j++ {
		if j < l {
			ø.vars[ø.proto.kTable[j].String()] = args[j]
		} else {
			ø.vars[ø.proto.kTable[j].String()] = Nil
		}
	}
	// Keep the args array
	ø.args = args

	// Execute the instructions
	for {
		// Get the instruction to process
		i := ø.proto.code[ø.pc]
		// Decode the instruction
		op, flg, ix := i.Opcode(), i.Flag(), i.Index()
		// Increment the PC, if a jump requires a different PC delta, it will set it explicitly
		ø.pc++
		switch op {
		case OP_RET:
			// End this function call, return the value on top of the stack
			return ø.pop()

		case OP_LOAD:
			v, err := ø.proto.ctx.Load(ø.getVal(flg, ix).String())
			if err != nil {
				panic(err)
			}
			ø.push(v)

		case OP_PUSH:
			ø.push(ø.getVal(flg, ix))

		case OP_POP:
			if nm, v := ø.proto.kTable[ix].String(), ø.pop(); !ø.proto.ctx.setVar(nm, v) {
				// Not found anywhere, create variable locally
				ø.vars[nm] = v
			}

		case OP_ADD:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Add(y))

		case OP_SUB:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Sub(y))

		case OP_MUL:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Mul(y))

		case OP_DIV:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Div(y))

		case OP_MOD:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Mod(y))

		case OP_POW:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Pow(y))

		case OP_NOT:
			x := ø.pop()
			ø.push(ø.proto.ctx.Logic.Not(x))

		case OP_UNM:
			x := ø.pop()
			ø.push(x.Unm())

		case OP_CALL:
			// ix is the number of args
			// Pop the function itself, ensure it is a function
			x := ø.pop()
			f, ok := x.(Func)
			if !ok {
				// TODO : Make an ErrXxx
				panic("call on a non-function value")
			}
			// Pop the arguments in reverse order
			args := make([]Val, ix)
			for j := ix; j > 0; j-- {
				args[j-1] = ø.pop()
			}
			// Call the function, and store the return value on the stack
			ø.push(f.Call(args...))

		case OP_EQ:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp == 0))

		case OP_LT:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp < 0))

		case OP_LTE:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp <= 0))

		case OP_GT:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp > 0))

		case OP_GTE:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp >= 0))

		case OP_AND:
			y, x := ø.pop(), ø.pop()
			ø.push(ø.proto.ctx.Logic.And(x, y))

		case OP_OR:
			y, x := ø.pop(), ø.pop()
			ø.push(ø.proto.ctx.Logic.Or(x, y))

		case OP_TEST:
			if !ø.pop().Bool() {
				// Do the jump over ix instructions
				ø.pc += int(ix)
			}

		case OP_JMPB:
			// TODO : Eventually change to a single JMP with signed value
			ø.pc -= (int(ix) + 1) // +1 because pc is already on next instr

		case OP_JMPF:
			ø.pc += int(ix)

		case OP_NEW:
			ø.push(NewObject())

		case OP_DUMP:
			// Dumps `ix` number of stack traces
			ø.proto.ctx.dump(int(ix)) // TODO : check int value

		case OP_SFLD:
			vr, k, vl := ø.pop(), ø.pop(), ø.pop()
			if ob, ok := vr.(*Object); ok {
				ob.Set(k, vl)
			} else {
				panic(ErrValNotAnObject)
			}

		case OP_GFLD:
			vr, k := ø.pop(), ø.pop()
			if ob, ok := vr.(*Object); ok {
				ø.push(ob.Get(k))
			} else {
				panic(ErrValNotAnObject)
			}

		case OP_CFLD:
			vr, k := ø.pop(), ø.pop()
			// Pop the arguments in reverse order
			args := make([]Val, ix)
			for j := ix; j > 0; j-- {
				args[j-1] = ø.pop()
			}
			if ob, ok := vr.(*Object); ok {
				ø.push(ob.callMethod(k, args...))
			} else {
				panic(ErrValNotAnObject)
			}

		default:
			panic(fmt.Sprintf("unknown opcode %s", op))
		}
	}
}
<commit_msg>add instr info for Ks, vars and Fns in DUMP<commit_after>package runtime

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"math"
)

var (
	ErrValNotAnObject = errors.New("value is not an object")
)

type funcVM struct {
	proto *GoblinFunc
	pc    int
	vars  map[string]Val
	stack []Val
	sp    int
	this  Val
	args  []Val
}

func newFuncVM(proto *GoblinFunc) *funcVM {
	return &funcVM{
		proto,
		0,
		make(map[string]Val, proto.expVars),
		make([]Val, 0, proto.stackSz),
		0,
		nil,
		nil,
	}
}

func (ø *funcVM) push(v Val) {
	// Stack has to grow as needed, StackSz doesn't take into account the loops
	if ø.sp == len(ø.stack) {
		if ø.sp == cap(ø.stack) {
			fmt.Printf("DEBUG expanding stack of func %s, current size: %d\n", ø.proto.name, len(ø.stack))
		}
		ø.stack = append(ø.stack, v)
	} else {
		ø.stack[ø.sp] = v
	}
	ø.sp++
}

func (ø *funcVM) pop() Val {
	ø.sp--
	v := ø.stack[ø.sp]
	ø.stack[ø.sp] = Nil // free this reference for gc
	return v
}

func (ø *funcVM) getVal(flg Flag, ix uint64) Val {
	switch flg {
	case FLG_K:
		return ø.proto.kTable[ix]
	case FLG_V:
		// If not found, will return Nil, so the value is always fine
		v, _ := ø.proto.ctx.getVar(ø.proto.kTable[ix].String())
		return v
	case FLG_N:
		return Nil
	case FLG_T:
		return ø.this
	case FLG_F:
		return ø.proto.ctx.Protos[ix]
	case FLG_AA:
		return ø.args[ix]
	}
	panic(fmt.Sprintf("Func.getVal() - invalid flag value %d", flg))
}

func (ø *funcVM) dumpInstrInfo(w io.Writer, i Instr) {
	switch i.Flag() {
	case FLG_K:
		fmt.Fprintf(w, " ; %s", ø.proto.kTable[i.Index()].dump())
	case FLG_V:
		fmt.Fprintf(w, " ; var %s", ø.proto.kTable[i.Index()])
	case FLG_N:
		fmt.Fprintf(w, " ; %s", Nil.dump())
	case FLG_T:
		fmt.Fprint(w, " ; [this]")
	case FLG_F:
		fmt.Fprintf(w, " ; %s", ø.proto.ctx.Protos[i.Index()].dump())
	case FLG_AA:
		fmt.Fprintf(w, " ; args[%d]", i.Index())
	}
}

func (ø *funcVM) dump() string {
	buf := bytes.NewBuffer(nil)
	fmt.Fprintf(buf, "\n> %s\n", ø.proto.dump())
	// Constants
	fmt.Fprintf(buf, "  Constants:\n")
	for i, v := range ø.proto.kTable {
		fmt.Fprintf(buf, "    [%3d] %s\n", i, v.dump())
	}
	// Variables
	fmt.Fprintf(buf, "\n  Variables:\n")
	for k, v := range ø.vars {
		fmt.Fprintf(buf, "    %s = %s\n", k, v.dump())
	}
	// Stack
	fmt.Fprintf(buf, "\n  Stack:\n")
	i := int(math.Max(0, float64(ø.sp-5)))
	for i <= ø.sp {
		if i == ø.sp {
			fmt.Fprint(buf, "sp->")
		} else {
			fmt.Fprint(buf, "    ")
		}
		v := Val(Nil)
		if i < len(ø.stack) {
			v = ø.stack[i]
		}
		fmt.Fprintf(buf, "[%3d] %s\n", i, v.dump())
		i++
	}
	// Instructions
	fmt.Fprintf(buf, "\n  Instructions:\n")
	i = int(math.Max(0, float64(ø.pc-10)))
	for i <= ø.pc+10 {
		if i == ø.pc {
			fmt.Fprintf(buf, "pc->")
		} else {
			fmt.Fprintf(buf, "    ")
		}
		if i < len(ø.proto.code) {
			fmt.Fprintf(buf, "[%3d] %s", i, ø.proto.code[i])
			ø.dumpInstrInfo(buf, ø.proto.code[i])
			fmt.Fprintln(buf)
		} else {
			break
		}
		i++
	}
	fmt.Fprintln(buf)
	return buf.String()
}

func (ø *funcVM) run(args ...Val) Val {
	// Expected args are defined in constant table spots 0 to ExpArgs - 1.
	for j, l := 0, len(args); j < ø.proto.expArgs; j++ {
		if j < l {
			ø.vars[ø.proto.kTable[j].String()] = args[j]
		} else {
			ø.vars[ø.proto.kTable[j].String()] = Nil
		}
	}
	// Keep the args array
	ø.args = args

	// Execute the instructions
	for {
		// Get the instruction to process
		i := ø.proto.code[ø.pc]
		// Decode the instruction
		op, flg, ix := i.Opcode(), i.Flag(), i.Index()
		// Increment the PC, if a jump requires a different PC delta, it will set it explicitly
		ø.pc++
		switch op {
		case OP_RET:
			// End this function call, return the value on top of the stack
			return ø.pop()

		case OP_LOAD:
			v, err := ø.proto.ctx.Load(ø.getVal(flg, ix).String())
			if err != nil {
				panic(err)
			}
			ø.push(v)

		case OP_PUSH:
			ø.push(ø.getVal(flg, ix))

		case OP_POP:
			if nm, v := ø.proto.kTable[ix].String(), ø.pop(); !ø.proto.ctx.setVar(nm, v) {
				// Not found anywhere, create variable locally
				ø.vars[nm] = v
			}

		case OP_ADD:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Add(y))

		case OP_SUB:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Sub(y))

		case OP_MUL:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Mul(y))

		case OP_DIV:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Div(y))

		case OP_MOD:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Mod(y))

		case OP_POW:
			y, x := ø.pop(), ø.pop()
			ø.push(x.Pow(y))

		case OP_NOT:
			x := ø.pop()
			ø.push(ø.proto.ctx.Logic.Not(x))

		case OP_UNM:
			x := ø.pop()
			ø.push(x.Unm())

		case OP_CALL:
			// ix is the number of args
			// Pop the function itself, ensure it is a function
			x := ø.pop()
			f, ok := x.(Func)
			if !ok {
				// TODO : Make an ErrXxx
				panic("call on a non-function value")
			}
			// Pop the arguments in reverse order
			args := make([]Val, ix)
			for j := ix; j > 0; j-- {
				args[j-1] = ø.pop()
			}
			// Call the function, and store the return value on the stack
			ø.push(f.Call(args...))

		case OP_EQ:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp == 0))

		case OP_LT:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp < 0))

		case OP_LTE:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp <= 0))

		case OP_GT:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp > 0))

		case OP_GTE:
			y, x := ø.pop(), ø.pop()
			cmp := x.Cmp(y)
			ø.push(Bool(cmp >= 0))

		case OP_AND:
			y, x := ø.pop(), ø.pop()
			ø.push(ø.proto.ctx.Logic.And(x, y))

		case OP_OR:
			y, x := ø.pop(), ø.pop()
			ø.push(ø.proto.ctx.Logic.Or(x, y))

		case OP_TEST:
			if !ø.pop().Bool() {
				// Do the jump over ix instructions
				ø.pc += int(ix)
			}

		case OP_JMPB:
			// TODO : Eventually change to a single JMP with signed value
			ø.pc -= (int(ix) + 1) // +1 because pc is already on next instr

		case OP_JMPF:
			ø.pc += int(ix)

		case OP_NEW:
			ø.push(NewObject())

		case OP_DUMP:
			// Dumps `ix` number of stack traces
			ø.proto.ctx.dump(int(ix)) // TODO : check int value

		case OP_SFLD:
			vr, k, vl := ø.pop(), ø.pop(), ø.pop()
			if ob, ok := vr.(*Object); ok {
				ob.Set(k, vl)
			} else {
				panic(ErrValNotAnObject)
			}

		case OP_GFLD:
			vr, k := ø.pop(), ø.pop()
			if ob, ok := vr.(*Object); ok {
				ø.push(ob.Get(k))
			} else {
				panic(ErrValNotAnObject)
			}

		case OP_CFLD:
			vr, k := ø.pop(), ø.pop()
			// Pop the arguments in reverse order
			args := make([]Val, ix)
			for j := ix; j > 0; j-- {
				args[j-1] = ø.pop()
			}
			if ob, ok := vr.(*Object); ok {
				ø.push(ob.callMethod(k, args...))
			} else {
				panic(ErrValNotAnObject)
			}

		default:
			panic(fmt.Sprintf("unknown opcode %s", op))
		}
	}
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/alphagov/router/logger"
)

const (
	cacheDuration = 30 * time.Minute

	redirectHandlerType               = "redirect-handler"
	pathPreservingRedirectHandlerType = "path-preserving-redirect-handler"
)

func NewRedirectHandler(source, target string, preserve bool, temporary bool) http.Handler {
	statusMoved := http.StatusMovedPermanently
	if temporary {
		statusMoved = http.StatusFound
	}
	if preserve {
		return &pathPreservingRedirectHandler{source, target, statusMoved}
	}
	return &redirectHandler{target, statusMoved}
}

func addCacheHeaders(writer http.ResponseWriter) {
	writer.Header().Set("Expires", time.Now().Add(cacheDuration).Format(time.RFC1123))
	writer.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", cacheDuration/time.Second))
}

func addGAQueryParam(target string, request *http.Request) string {
	if ga := request.URL.Query().Get("_ga"); ga != "" {
		u, err := url.Parse(target)
		if err != nil {
			defer logger.NotifySentry(logger.ReportableError{Error: err, Request: request})
			return target
		}
		values := u.Query()
		values.Set("_ga", ga)
		u.RawQuery = values.Encode()
		return u.String()
	}
	return target
}

type redirectHandler struct {
	url  string
	code int
}

func (handler *redirectHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	addCacheHeaders(writer)

	target := addGAQueryParam(handler.url, request)
	http.Redirect(writer, request, target, handler.code)

	RedirectHandlerRedirectCountMetric.With(prometheus.Labels{
		"redirect_type": redirectHandlerType,
		"redirect_code": fmt.Sprintf("%d", handler.code),
		"redirect_url":  handler.url,
	}).Inc()
}

type pathPreservingRedirectHandler struct {
	sourcePrefix string
	targetPrefix string
	code         int
}

func (handler *pathPreservingRedirectHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	target := handler.targetPrefix + strings.TrimPrefix(request.URL.Path, handler.sourcePrefix)

	if request.URL.RawQuery != "" {
		target = target + "?" + request.URL.RawQuery
	}

	addCacheHeaders(writer)
	http.Redirect(writer, request, target, handler.code)

	RedirectHandlerRedirectCountMetric.With(prometheus.Labels{
		"redirect_code": fmt.Sprintf("%d", handler.code),
		"redirect_type": pathPreservingRedirectHandlerType,
		"redirect_url":  stripQuery(target),
	}).Inc()
}

func stripQuery(us string) string {
	u, _ := url.Parse(us)
	u.RawQuery = ""
	return u.String()
}
<commit_msg>Add comment clarifying use of stripQuery<commit_after>package handlers

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/alphagov/router/logger"
)

const (
	cacheDuration = 30 * time.Minute

	redirectHandlerType               = "redirect-handler"
	pathPreservingRedirectHandlerType = "path-preserving-redirect-handler"
)

func NewRedirectHandler(source, target string, preserve bool, temporary bool) http.Handler {
	statusMoved := http.StatusMovedPermanently
	if temporary {
		statusMoved = http.StatusFound
	}
	if preserve {
		return &pathPreservingRedirectHandler{source, target, statusMoved}
	}
	return &redirectHandler{target, statusMoved}
}

func addCacheHeaders(writer http.ResponseWriter) {
	writer.Header().Set("Expires", time.Now().Add(cacheDuration).Format(time.RFC1123))
	writer.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", cacheDuration/time.Second))
}

func addGAQueryParam(target string, request *http.Request) string {
	if ga := request.URL.Query().Get("_ga"); ga != "" {
		u, err := url.Parse(target)
		if err != nil {
			defer logger.NotifySentry(logger.ReportableError{Error: err, Request: request})
			return target
		}
		values := u.Query()
		values.Set("_ga", ga)
		u.RawQuery = values.Encode()
		return u.String()
	}
	return target
}

type redirectHandler struct {
	url  string
	code int
}

func (handler *redirectHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	addCacheHeaders(writer)

	target := addGAQueryParam(handler.url, request)
	http.Redirect(writer, request, target, handler.code)

	RedirectHandlerRedirectCountMetric.With(prometheus.Labels{
		"redirect_type": redirectHandlerType,
		"redirect_code": fmt.Sprintf("%d", handler.code),
		"redirect_url":  handler.url,
	}).Inc()
}

type pathPreservingRedirectHandler struct {
	sourcePrefix string
	targetPrefix string
	code         int
}

func (handler *pathPreservingRedirectHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	target := handler.targetPrefix + strings.TrimPrefix(request.URL.Path, handler.sourcePrefix)

	if request.URL.RawQuery != "" {
		target = target + "?" + request.URL.RawQuery
	}

	addCacheHeaders(writer)
	http.Redirect(writer, request, target, handler.code)

	RedirectHandlerRedirectCountMetric.With(prometheus.Labels{
		"redirect_code": fmt.Sprintf("%d", handler.code),
		"redirect_type": pathPreservingRedirectHandlerType,
		"redirect_url":  stripQuery(target),
	}).Inc()
}

// stripQuery should only be called with valid URL
// stripQuery should only be used for recording the URL used by the handler
func stripQuery(us string) string {
	u, _ := url.Parse(us)
	u.RawQuery = ""
	return u.String()
}
<|endoftext|>


<commit_before>package main
import "os"
import "os/exec"
import "strings"
import "sync"
import "os/signal"
import "syscall"
import "github.com/robfig/cron"

func execute(command string, args []string)() {

    println("executing:", command, strings.Join(args, " "))

    cmd := exec.Command(command, args...)

    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    cmd.Run()

    cmd.Wait()
}

func create() (cr *cron.Cron, wgr *sync.WaitGroup) {
    var schedule string = os.Args[1]
    var command string = os.Args[2]
    var args []string = os.Args[3:len(os.Args)]

    wg := &sync.WaitGroup{}

    c := cron.New()
    println("new cron:", schedule)

    c.AddFunc(schedule, func() {
        wg.Add(1)
        execute(command, args)
        wg.Done()
    })

    return c, wg
}

func start(c *cron.Cron, wg *sync.WaitGroup) {
    c.Start()
}

func stop(c *cron.Cron, wg *sync.WaitGroup) {
    println("Stopping")
    c.Stop()
    println("Waiting")
    wg.Wait()
    println("Exiting")
    os.Exit(0)
}

func main() {

    c, wg := create()

    go start(c, wg)

    ch := make(chan os.Signal, 1)
    signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
    println(<-ch)

    stop(c, wg)
}

<commit_msg>fix for dayli use<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/robfig/cron"
)

func execute(command string, args []string) {
	log.Printf("executing: %s %s", command, strings.Join(args, " "))

	cmd := exec.Command(command, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Run()
}

func create() (cr *cron.Cron, wgr *sync.WaitGroup) {
	var schedule string = os.Args[1]
	var command string = os.Args[2]
	var args []string = os.Args[3:len(os.Args)]

	wg := &sync.WaitGroup{}

	c := cron.New()
	println("new cron:", schedule)

	c.AddFunc(schedule, func() {
		wg.Add(1)
		execute(command, args)
		wg.Done()
	})

	return c, wg
}

func start(c *cron.Cron, wg *sync.WaitGroup) {
	c.Start()
}

func stop(c *cron.Cron, wg *sync.WaitGroup) {
	println("Stopping")
	c.Stop()
	println("Waiting")
	wg.Wait()
	println("Exiting")
	os.Exit(0)
}

func main() {
	flag.Parse()

	c, wg := create()
	go start(c, wg)

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	<-ch

	stop(c, wg)
}
<|endoftext|>


<commit_before>package nuance

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"
	"testing"
)

// place your OEM code in a plain text file
const oemLicenseTxtFile = "/src/license.txt"

// replace it with your license file
const oemLicenseFile = "/src/license.lcxz"

func loadlicenseTxt() (r string) {
	code, err := ioutil.ReadFile(oemLicenseTxtFile)
	if err != nil {
		log.Fatal("Error loading license file", oemLicenseTxtFile, err)
	}

	r = strings.TrimSpace(string(code))

	return r
}

func TestSetLicense(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()
	err := n.SetLicense(oemLicenseFile, oemCode)

	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	n.Quit()
	n.Free()
}

func TestInit(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.Init("YOUR_COMPANY", "YOUR_PRODUCT")
	if err != nil {
		t.Fatal("Init failed:", err)
	}

	n.Quit()
	n.Free()

}

func TestLoadFormTemplateLibrary(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.LoadFormTemplateLibrary("/src/template.ftl")
	if err != nil {
		t.Fatal("LoadFormTemplateLibrary failed:", err)
	}

	n.Quit()
	n.Free()

}

func TestOCRImgWithTemplate(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.Init("YOUR_COMPANY", "YOUR_PRODUCT")
	if err != nil {
		t.Fatal("Init failed:", err)
	}

	err = n.LoadFormTemplateLibrary("/src/template.ftl")
	if err != nil {
		t.Fatal("LoadFormTemplateLibrary failed:", err)
	}

	var ret map[string]string
	ret, err = n.OCRImgWithTemplate("/src/sample.tif")
	if err != nil {
		t.Fatal("OCRImgWithTemplate failed:", err)
	}

	for k, v := range ret {
		fmt.Println("k:", k, "v:", v)
	}

	n.Quit()
	n.Free()
}
<commit_msg>created test for OCRImgToText<commit_after>package nuance

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"
	"testing"
)

// place your OEM code in a plain text file
const oemLicenseTxtFile = "/src/license.txt"

// replace it with your license file
const oemLicenseFile = "/src/license.lcxz"

func loadlicenseTxt() (r string) {
	code, err := ioutil.ReadFile(oemLicenseTxtFile)
	if err != nil {
		log.Fatal("Error loading license file", oemLicenseTxtFile, err)
	}

	r = strings.TrimSpace(string(code))

	return r
}

func TestSetLicense(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()
	err := n.SetLicense(oemLicenseFile, oemCode)

	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	n.Quit()
	n.Free()
}

func TestInit(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.Init("YOUR_COMPANY", "YOUR_PRODUCT")
	if err != nil {
		t.Fatal("Init failed:", err)
	}

	n.Quit()
	n.Free()

}

func TestLoadFormTemplateLibrary(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.LoadFormTemplateLibrary("/src/template.ftl")
	if err != nil {
		t.Fatal("LoadFormTemplateLibrary failed:", err)
	}

	n.Quit()
	n.Free()

}

func TestOCRImgWithTemplate(t *testing.T) {

	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.Init("YOUR_COMPANY", "YOUR_PRODUCT")
	if err != nil {
		t.Fatal("Init failed:", err)
	}

	err = n.LoadFormTemplateLibrary("/src/template.ftl")
	if err != nil {
		t.Fatal("LoadFormTemplateLibrary failed:", err)
	}

	var ret map[string]string
	ret, err = n.OCRImgWithTemplate("/src/sample.tif")
	if err != nil {
		t.Fatal("OCRImgWithTemplate failed:", err)
	}

	for k, v := range ret {
		fmt.Println("k:", k, "v:", v)
	}

	n.Quit()
	n.Free()
}

func TestOCRImgToText(t *testing.T) {
	oemCode := loadlicenseTxt()

	n := New()

	err := n.SetLicense(oemLicenseFile, oemCode)
	if err != nil {
		t.Fatal("SetLicense failed:", err)
	}

	err = n.Init("YOUR_COMPANY", "YOUR_PRODUCT")
	if err != nil {
		t.Fatal("Init failed:", err)
	}

	err = n.OCRImgToText("/src/sample.tif",
		"/src/sample.txt",
		"/src/sample.doc")
	if err != nil {
		t.Fatal("OCRImgToText failed:", err)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"

	r "reflect"

	sci "github.com/samuell/scipipe"
)

func main() {
	p := NewFooToBarReplacer()
	fmt.Println(p)
}

type FooToBarReplacer struct {
	sci.Process
	InnerProcess *sci.Process
	InFoo        chan *sci.FileTarget
	OutBar       chan *sci.FileTarget
}

func (p *FooToBarReplacer) Run() {
	// ...
}

func NewFooToBarReplacer() interface{} {
	execFunc := func(task *sci.ShellTask) {
		indata := task.InTargets["foo"].Read()
		indataReplaced := bytes.Replace(indata, []byte("foo"), []byte("bar"), -1)
		task.OutTargets["bar"].WriteTempFile(indataReplaced)
	}
	pathFuncs := map[string]func(*sci.ShellTask) string{
		"bar": func(t *sci.ShellTask) string { return t.InTargets["foo"].GetPath() + ".bar.txt" },
	}
	return NewProcessFromStruct(&FooToBarReplacer{}, execFunc, pathFuncs)
}

func NewProcessFromStruct(procStruct interface{}, execFunc func(*sci.ShellTask), pathFuncs map[string]func(*sci.ShellTask) string) interface{} {
	// Get in-ports of struct
	psVal := r.ValueOf(procStruct).Elem()
	for i := 0; i < psVal.NumField(); i++ {
		fmt.Println(psVal.Type().Field(i).Name)
	}
	return procStruct
}
<commit_msg>Filter on struct field type as well<commit_after>package main

import (
	"bytes"
	"fmt"

	r "reflect"

	"strings"

	sci "github.com/samuell/scipipe"
)

func main() {
	p := NewFooToBarReplacer()
	fmt.Println("Process: ", p)
}

type FooToBarReplacer struct {
	sci.Process
	InnerProcess *sci.Process
	InFoo        chan *sci.FileTarget
	OutBar       chan *sci.FileTarget
}

func (p *FooToBarReplacer) Run() {
	// ...
}

func NewFooToBarReplacer() interface{} {
	execFunc := func(task *sci.ShellTask) {
		indata := task.InTargets["foo"].Read()
		indataReplaced := bytes.Replace(indata, []byte("foo"), []byte("bar"), -1)
		task.OutTargets["bar"].WriteTempFile(indataReplaced)
	}
	pathFuncs := map[string]func(*sci.ShellTask) string{
		"bar": func(t *sci.ShellTask) string { return t.InTargets["foo"].GetPath() + ".bar.txt" },
	}
	return NewProcessFromStruct(&FooToBarReplacer{}, execFunc, pathFuncs)
}

func NewProcessFromStruct(procStruct interface{}, execFunc func(*sci.ShellTask), pathFuncs map[string]func(*sci.ShellTask) string) interface{} {
	// Get in-ports of struct
	procStructVal := r.ValueOf(procStruct).Elem()
	for i := 0; i < procStructVal.NumField(); i++ {
		structFieldName := procStructVal.Type().Field(i).Name
		structFieldType := procStructVal.Type().Field(i).Type
		exampleChan := make(chan *sci.FileTarget)
		if strings.HasPrefix(structFieldName, "In") && structFieldType == r.TypeOf(exampleChan) {
			fmt.Println("In-port:", structFieldName)
		} else if strings.HasPrefix(structFieldName, "Out") && structFieldType == r.TypeOf(exampleChan) {
			fmt.Println("Out-port:", structFieldName)
		}
	}
	return procStruct
}
<|endoftext|>