<commit_before>package service

import (
	"fmt"
	"path"
	"time"

	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/coordinator/client"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/service"
	"github.com/zenoss/serviced/domain/servicestate"
	zkutils "github.com/zenoss/serviced/zzk/utils"
)

const (
	zkHost = "/hosts"
)

func hostpath(nodes ...string) string {
	p := append([]string{zkHost}, nodes...)
	return path.Join(p...)
}

// HostState is the zookeeper node for storing service instance information
// per host
type HostState struct {
	HostID         string
	ServiceID      string
	ServiceStateID string
	DesiredState   int
	version        interface{}
}

// NewHostState instantiates a new HostState node for client.Node
func NewHostState(state *servicestate.ServiceState) *HostState {
	return &HostState{
		HostID:         state.HostID,
		ServiceID:      state.ServiceID,
		ServiceStateID: state.ID,
		DesiredState:   service.SVCRun,
	}
}

// Version inplements client.Node
func (node *HostState) Version() interface{} {
	return node.version
}

// SetVersion implements client.Node
func (node *HostState) SetVersion(version interface{}) {
	node.version = version
}

// HostHandler is the handler for running the HostListener
type HostStateHandler interface {
	GetHost(string) (*host.Host, error)
	AttachService(chan<- interface{}, *service.Service, *servicestate.ServiceState) error
	StartService(chan<- interface{}, *service.Service, *servicestate.ServiceState) error
	StopService(*servicestate.ServiceState) error
}

// HostStateListener is the listener for monitoring service instances
type HostStateListener struct {
	conn    client.Connection
	handler HostStateHandler
	hostID  string
}

// NewHostListener instantiates a HostListener object
func NewHostStateListener(conn client.Connection, handler HostStateHandler, hostID string) *HostStateListener {
	return &HostStateListener{
		conn:    conn,
		handler: handler,
		hostID:  hostID,
	}
}

// Listen starts the HostListener by monitoring when new service instances are
// started, updated, or removed
func (l *HostStateListener) Listen(shutdown <-chan interface{}) {
	var (
		_shutdown  = make(chan interface{})
		done       = make(chan string)
		processing = make(map[string]interface{})
	)

	// Register the host
	regpath, err := l.register(shutdown)
	if err != nil {
		glog.Errorf("Could not register host %s: %s", l.hostID, err)
		return
	}

	// Housekeeping
	defer func() {
		glog.Info("HostStateListener receieved interrupt")
		close(_shutdown)
		for len(processing) > 0 {
			delete(processing, <-done)
		}
		if err := l.conn.Delete(regpath); err != nil {
			glog.Warning("Could not unregister host %s: %s", l.hostID, err)
		}
	}()

	// Monitor the instances
	hpath := hostpath(l.hostID)
	for {
		stateIDs, event, err := l.conn.ChildrenW(hpath)
		if err != nil {
			glog.Errorf("Could not watch for states on host %s: %s", l.hostID, err)
			return
		}

		for _, ssid := range stateIDs {
			if _, ok := processing[ssid]; !ok {
				glog.V(1).Info("Spawning a listener for %s", ssid)
				processing[ssid] = nil
				go l.listenHostState(_shutdown, done, ssid)
			}
		}

		select {
		case e := <-event:
			if e.Type == client.EventNodeDeleted {
				glog.Infof("Host has been removed from pool, shutting down listener")
				return
			}
			glog.V(2).Infof("Received event: %v", e)
		case ssid := <-done:
			glog.V(2).Info("Cleaning up %s", ssid)
			delete(processing, ssid)
		case <-shutdown:
			return
		}
	}
}

func (l *HostStateListener) listenHostState(shutdown <-chan interface{}, done chan<- string, ssID string) {
	defer func() {
		glog.V(2).Info("Shutting down listener for host instance ", ssID)
		done <- ssID
	}()

	var processDone <-chan interface{}
	hpath := hostpath(l.hostID, ssID)
	for {
		var hs HostState
		event, err := l.conn.GetW(hpath, &hs)
		if err != nil {
			glog.Errorf("Could not load host instance %s: %s", ssID, err)
			return
		}

		if hs.ServiceID == "" || hs.ServiceStateID == "" {
			glog.Error("Invalid host state instance: ", hpath)
			return
		}

		var state servicestate.ServiceState
		if err := l.conn.Get(servicepath(hs.ServiceID, hs.ServiceStateID), &ServiceStateNode{ServiceState: &state}); err != nil {
			glog.Error("Could not find service instance: ", hs.ServiceStateID)
			// Node doesn't exist or cannot be loaded, delete
			if err := l.conn.Delete(hpath); err != nil {
				glog.Warningf("Could not delete host state %s: %s", ssID, err)
			}
			return
		}

		var svc service.Service
		if err := l.conn.Get(servicepath(hs.ServiceID), &ServiceNode{Service: &svc}); err != nil {
			glog.Error("Could not find service: ", hs.ServiceID)
			return
		}

		glog.V(2).Infof("Processing %s (%s); Desired State: %d", svc.Name, svc.ID, hs.DesiredState)
		switch hs.DesiredState {
		case service.SVCRun:
			var err error
			if state.Started.UnixNano() <= state.Terminated.UnixNano() {
				processDone, err = l.startInstance(&svc, &state)
			} else if processDone == nil {
				processDone, err = l.attachInstance(&svc, &state)
			}
			if err != nil {
				glog.Errorf("Error trying to start or attach to service instance %s: %s", state.ID, err)
				l.stopInstance(&state)
				return
			}
		case service.SVCStop:
			if processDone != nil {
				l.detachInstance(processDone, &state)
			} else {
				l.stopInstance(&state)
			}
			return
		default:
			glog.V(2).Infof("Unhandled service %s (%s)", svc.Name, svc.ID)
		}

		select {
		case <-processDone:
			glog.V(2).Infof("Process ended for instance: ", hs.ServiceStateID)
			processDone = nil
		case e := <-event:
			glog.V(3).Info("Receieved event: ", e)
			if e.Type == client.EventNodeDeleted {
				if processDone != nil {
					l.detachInstance(processDone, &state)
				} else {
					l.stopInstance(&state)
				}
				return
			}
		case <-shutdown:
			glog.V(0).Infof("Service %s Host instance %s receieved signal to shutdown", hs.ServiceID, hs.ServiceStateID)
			if processDone != nil {
				glog.V(0).Infof("detaching from %s; %s", hs.ServiceID, hs.ServiceStateID)
				l.detachInstance(processDone, &state)

			} else {
				glog.V(0).Infof("stopping from %s; %s", hs.ServiceID, hs.ServiceStateID)
				l.stopInstance(&state)
				glog.V(0).Infof("stopped from %s; %s", hs.ServiceID, hs.ServiceStateID)

			}
			return
		}
	}
}

func (l *HostStateListener) updateInstance(done <-chan interface{}, state *servicestate.ServiceState) (<-chan interface{}, error) {
	wait := make(chan interface{})
	go func(path string) {
		defer close(wait)
		<-done
		var s servicestate.ServiceState
		if err := l.conn.Get(path, &ServiceStateNode{ServiceState: &s}); err != nil {
			glog.Warningf("Could not get service state %s: %s", state.ID, err)
			return
		}

		s.Terminated = time.Now()
		if err := updateInstance(l.conn, &s); err != nil {
			glog.Warningf("Could not update the service instance %s with the time terminated (%s): %s", s.ID, s.Terminated.UnixNano(), err)
			return
		}
	}(servicepath(state.ServiceID, state.ID))

	return wait, updateInstance(l.conn, state)
}

func (l *HostStateListener) startInstance(svc *service.Service, state *servicestate.ServiceState) (<-chan interface{}, error) {
	done := make(chan interface{})
	if err := l.handler.StartService(done, svc, state); err != nil {
		return nil, err
	}

	wait, err := l.updateInstance(done, state)
	if err != nil {
		return nil, err
	}

	return wait, nil
}

func (l *HostStateListener) attachInstance(svc *service.Service, state *servicestate.ServiceState) (<-chan interface{}, error) {
	done := make(chan interface{})
	if err := l.handler.AttachService(done, svc, state); err != nil {
		return nil, err
	}

	wait, err := l.updateInstance(done, state)
	if err != nil {
		return nil, err
	}

	return wait, nil
}

func (l *HostStateListener) stopInstance(state *servicestate.ServiceState) error {
	if err := l.handler.StopService(state); err != nil {
		return err
	}
	return removeInstance(l.conn, state)
}

func (l *HostStateListener) detachInstance(done <-chan interface{}, state *servicestate.ServiceState) error {
	if err := l.handler.StopService(state); err != nil {
		return err
	}
	<-done
	return removeInstance(l.conn, state)
}

// register waits for the leader to initialize the host
func (l *HostStateListener) register(shutdown <-chan interface{}) (string, error) {
	// wait for /hosts
	for {
		exists, err := zkutils.PathExists(l.conn, hostpath())
		if err != nil {
			return "", err
		}
		if exists {
			break
		}
		_, event, err := l.conn.ChildrenW("/")
		if err != nil {
			return "", err
		}
		select {
		case <-event:
		case <-shutdown:
			return "", ErrShutdown
		}
	}

	// wait for /hosts/HOSTID
	for {
		exists, err := zkutils.PathExists(l.conn, hostpath(l.hostID))
		if err != nil {
			return "", err
		}
		if exists {
			break
		}
		_, event, err := l.conn.ChildrenW(hostpath())
		if err != nil {
			return "", err
		}
		select {
		case <-event:
		case <-shutdown:
			return "", ErrShutdown
		}
	}

	host, err := l.handler.GetHost(l.hostID)
	if err != nil {
		return "", err
	} else if host == nil {
		return "", ErrHostInvalid
	}

	return l.conn.CreateEphemeral(hostregpath(l.hostID), &HostNode{Host: host})
}

func addInstance(conn client.Connection, state *servicestate.ServiceState) error {
	if state.ID == "" {
		return fmt.Errorf("missing service state id")
	} else if state.ServiceID == "" {
		return fmt.Errorf("missing service id")
	}

	var (
		spath = servicepath(state.ServiceID, state.ID)
		node  = &ServiceStateNode{ServiceState: state}
	)

	if err := conn.Create(spath, node); err != nil {
		return err
	} else if err := conn.Create(hostpath(state.HostID, state.ID), NewHostState(state)); err != nil {
		// try to clean up if create fails
		if err := conn.Delete(spath); err != nil {
			glog.Warningf("Could not remove service instance %s: %s", state.ID, err)
		}
		return err
	}
	return nil
}

func updateInstance(conn client.Connection, state *servicestate.ServiceState) error {
	return conn.Set(servicepath(state.ServiceID, state.ID), &ServiceStateNode{ServiceState: state})
}

func removeInstance(conn client.Connection, state *servicestate.ServiceState) error {
	if err := conn.Delete(hostpath(state.HostID, state.ID)); err != nil {
		glog.Warningf("Could not delete host state %s: %s", state.HostID, state.ID)
	}
	return conn.Delete(servicepath(state.ServiceID, state.ID))
}

func StopServiceInstance(conn client.Connection, hostID, stateID string) error {
	hpath := hostpath(hostID, stateID)
	var hs HostState
	if err := conn.Get(hpath, &hs); err != nil {
		return err
	}
	glog.V(2).Infof("Stopping instance %s via host %s", stateID, hostID)
	hs.DesiredState = service.SVCStop
	return conn.Set(hpath, &hs)
}
<commit_msg>time out detaching from host<commit_after>package service

import (
	"fmt"
	"path"
	"time"

	"github.com/zenoss/glog"
	"github.com/zenoss/serviced/coordinator/client"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/service"
	"github.com/zenoss/serviced/domain/servicestate"
	zkutils "github.com/zenoss/serviced/zzk/utils"
)

const (
	zkHost = "/hosts"
)

func hostpath(nodes ...string) string {
	p := append([]string{zkHost}, nodes...)
	return path.Join(p...)
}

// HostState is the zookeeper node for storing service instance information
// per host
type HostState struct {
	HostID         string
	ServiceID      string
	ServiceStateID string
	DesiredState   int
	version        interface{}
}

// NewHostState instantiates a new HostState node for client.Node
func NewHostState(state *servicestate.ServiceState) *HostState {
	return &HostState{
		HostID:         state.HostID,
		ServiceID:      state.ServiceID,
		ServiceStateID: state.ID,
		DesiredState:   service.SVCRun,
	}
}

// Version inplements client.Node
func (node *HostState) Version() interface{} {
	return node.version
}

// SetVersion implements client.Node
func (node *HostState) SetVersion(version interface{}) {
	node.version = version
}

// HostHandler is the handler for running the HostListener
type HostStateHandler interface {
	GetHost(string) (*host.Host, error)
	AttachService(chan<- interface{}, *service.Service, *servicestate.ServiceState) error
	StartService(chan<- interface{}, *service.Service, *servicestate.ServiceState) error
	StopService(*servicestate.ServiceState) error
}

// HostStateListener is the listener for monitoring service instances
type HostStateListener struct {
	conn    client.Connection
	handler HostStateHandler
	hostID  string
}

// NewHostListener instantiates a HostListener object
func NewHostStateListener(conn client.Connection, handler HostStateHandler, hostID string) *HostStateListener {
	return &HostStateListener{
		conn:    conn,
		handler: handler,
		hostID:  hostID,
	}
}

// Listen starts the HostListener by monitoring when new service instances are
// started, updated, or removed
func (l *HostStateListener) Listen(shutdown <-chan interface{}) {
	var (
		_shutdown  = make(chan interface{})
		done       = make(chan string)
		processing = make(map[string]interface{})
	)

	// Register the host
	regpath, err := l.register(shutdown)
	if err != nil {
		glog.Errorf("Could not register host %s: %s", l.hostID, err)
		return
	}

	// Housekeeping
	defer func() {
		glog.Info("HostStateListener receieved interrupt")
		close(_shutdown)
		for len(processing) > 0 {
			delete(processing, <-done)
		}
		if err := l.conn.Delete(regpath); err != nil {
			glog.Warning("Could not unregister host %s: %s", l.hostID, err)
		}
	}()

	// Monitor the instances
	hpath := hostpath(l.hostID)
	for {
		stateIDs, event, err := l.conn.ChildrenW(hpath)
		if err != nil {
			glog.Errorf("Could not watch for states on host %s: %s", l.hostID, err)
			return
		}

		for _, ssid := range stateIDs {
			if _, ok := processing[ssid]; !ok {
				glog.V(1).Info("Spawning a listener for %s", ssid)
				processing[ssid] = nil
				go l.listenHostState(_shutdown, done, ssid)
			}
		}

		select {
		case e := <-event:
			if e.Type == client.EventNodeDeleted {
				glog.Infof("Host has been removed from pool, shutting down listener")
				return
			}
			glog.V(2).Infof("Received event: %v", e)
		case ssid := <-done:
			glog.V(2).Info("Cleaning up %s", ssid)
			delete(processing, ssid)
		case <-shutdown:
			return
		}
	}
}

func (l *HostStateListener) listenHostState(shutdown <-chan interface{}, done chan<- string, ssID string) {
	defer func() {
		glog.V(2).Info("Shutting down listener for host instance ", ssID)
		done <- ssID
	}()

	var processDone <-chan interface{}
	hpath := hostpath(l.hostID, ssID)
	for {
		var hs HostState
		event, err := l.conn.GetW(hpath, &hs)
		if err != nil {
			glog.Errorf("Could not load host instance %s: %s", ssID, err)
			return
		}

		if hs.ServiceID == "" || hs.ServiceStateID == "" {
			glog.Error("Invalid host state instance: ", hpath)
			return
		}

		var state servicestate.ServiceState
		if err := l.conn.Get(servicepath(hs.ServiceID, hs.ServiceStateID), &ServiceStateNode{ServiceState: &state}); err != nil {
			glog.Error("Could not find service instance: ", hs.ServiceStateID)
			// Node doesn't exist or cannot be loaded, delete
			if err := l.conn.Delete(hpath); err != nil {
				glog.Warningf("Could not delete host state %s: %s", ssID, err)
			}
			return
		}

		var svc service.Service
		if err := l.conn.Get(servicepath(hs.ServiceID), &ServiceNode{Service: &svc}); err != nil {
			glog.Error("Could not find service: ", hs.ServiceID)
			return
		}

		glog.V(2).Infof("Processing %s (%s); Desired State: %d", svc.Name, svc.ID, hs.DesiredState)
		switch hs.DesiredState {
		case service.SVCRun:
			var err error
			if state.Started.UnixNano() <= state.Terminated.UnixNano() {
				processDone, err = l.startInstance(&svc, &state)
			} else if processDone == nil {
				processDone, err = l.attachInstance(&svc, &state)
			}
			if err != nil {
				glog.Errorf("Error trying to start or attach to service instance %s: %s", state.ID, err)
				l.stopInstance(&state)
				return
			}
		case service.SVCStop:
			if processDone != nil {
				l.detachInstance(processDone, &state)
			} else {
				l.stopInstance(&state)
			}
			return
		default:
			glog.V(2).Infof("Unhandled service %s (%s)", svc.Name, svc.ID)
		}

		select {
		case <-processDone:
			glog.V(2).Infof("Process ended for instance: ", hs.ServiceStateID)
			processDone = nil
		case e := <-event:
			glog.V(3).Info("Receieved event: ", e)
			if e.Type == client.EventNodeDeleted {
				if processDone != nil {
					l.detachInstance(processDone, &state)
				} else {
					l.stopInstance(&state)
				}
				return
			}
		case <-shutdown:
			glog.V(0).Infof("Service %s Host instance %s receieved signal to shutdown", hs.ServiceID, hs.ServiceStateID)
			if processDone != nil {
				glog.V(0).Infof("detaching from %s; %s", hs.ServiceID, hs.ServiceStateID)
				l.detachInstance(processDone, &state)
				select {
				case <-processDone:
					glog.V(0).Infof("detached from %s; %s", hs.ServiceID, hs.ServiceStateID)
				case <-Time.After(60 * time.Second):
					glog.V(0).Infof("timed out detaching from %s; %s", hs.ServiceID, hs.ServiceStateID)
				}

			} else {
				glog.V(0).Infof("stopping from %s; %s", hs.ServiceID, hs.ServiceStateID)
				l.stopInstance(&state)
				glog.V(0).Infof("stopped from %s; %s", hs.ServiceID, hs.ServiceStateID)

			}
			return
		}
	}
}

func (l *HostStateListener) updateInstance(done <-chan interface{}, state *servicestate.ServiceState) (<-chan interface{}, error) {
	wait := make(chan interface{})
	go func(path string) {
		defer close(wait)
		<-done
		var s servicestate.ServiceState
		if err := l.conn.Get(path, &ServiceStateNode{ServiceState: &s}); err != nil {
			glog.Warningf("Could not get service state %s: %s", state.ID, err)
			return
		}

		s.Terminated = time.Now()
		if err := updateInstance(l.conn, &s); err != nil {
			glog.Warningf("Could not update the service instance %s with the time terminated (%s): %s", s.ID, s.Terminated.UnixNano(), err)
			return
		}
	}(servicepath(state.ServiceID, state.ID))

	return wait, updateInstance(l.conn, state)
}

func (l *HostStateListener) startInstance(svc *service.Service, state *servicestate.ServiceState) (<-chan interface{}, error) {
	done := make(chan interface{})
	if err := l.handler.StartService(done, svc, state); err != nil {
		return nil, err
	}

	wait, err := l.updateInstance(done, state)
	if err != nil {
		return nil, err
	}

	return wait, nil
}

func (l *HostStateListener) attachInstance(svc *service.Service, state *servicestate.ServiceState) (<-chan interface{}, error) {
	done := make(chan interface{})
	if err := l.handler.AttachService(done, svc, state); err != nil {
		return nil, err
	}

	wait, err := l.updateInstance(done, state)
	if err != nil {
		return nil, err
	}

	return wait, nil
}

func (l *HostStateListener) stopInstance(state *servicestate.ServiceState) error {
	if err := l.handler.StopService(state); err != nil {
		return err
	}
	return removeInstance(l.conn, state)
}

func (l *HostStateListener) detachInstance(done <-chan interface{}, state *servicestate.ServiceState) error {
	if err := l.handler.StopService(state); err != nil {
		return err
	}
	<-done
	return removeInstance(l.conn, state)
}

// register waits for the leader to initialize the host
func (l *HostStateListener) register(shutdown <-chan interface{}) (string, error) {
	// wait for /hosts
	for {
		exists, err := zkutils.PathExists(l.conn, hostpath())
		if err != nil {
			return "", err
		}
		if exists {
			break
		}
		_, event, err := l.conn.ChildrenW("/")
		if err != nil {
			return "", err
		}
		select {
		case <-event:
		case <-shutdown:
			return "", ErrShutdown
		}
	}

	// wait for /hosts/HOSTID
	for {
		exists, err := zkutils.PathExists(l.conn, hostpath(l.hostID))
		if err != nil {
			return "", err
		}
		if exists {
			break
		}
		_, event, err := l.conn.ChildrenW(hostpath())
		if err != nil {
			return "", err
		}
		select {
		case <-event:
		case <-shutdown:
			return "", ErrShutdown
		}
	}

	host, err := l.handler.GetHost(l.hostID)
	if err != nil {
		return "", err
	} else if host == nil {
		return "", ErrHostInvalid
	}

	return l.conn.CreateEphemeral(hostregpath(l.hostID), &HostNode{Host: host})
}

func addInstance(conn client.Connection, state *servicestate.ServiceState) error {
	if state.ID == "" {
		return fmt.Errorf("missing service state id")
	} else if state.ServiceID == "" {
		return fmt.Errorf("missing service id")
	}

	var (
		spath = servicepath(state.ServiceID, state.ID)
		node  = &ServiceStateNode{ServiceState: state}
	)

	if err := conn.Create(spath, node); err != nil {
		return err
	} else if err := conn.Create(hostpath(state.HostID, state.ID), NewHostState(state)); err != nil {
		// try to clean up if create fails
		if err := conn.Delete(spath); err != nil {
			glog.Warningf("Could not remove service instance %s: %s", state.ID, err)
		}
		return err
	}
	return nil
}

func updateInstance(conn client.Connection, state *servicestate.ServiceState) error {
	return conn.Set(servicepath(state.ServiceID, state.ID), &ServiceStateNode{ServiceState: state})
}

func removeInstance(conn client.Connection, state *servicestate.ServiceState) error {
	if err := conn.Delete(hostpath(state.HostID, state.ID)); err != nil {
		glog.Warningf("Could not delete host state %s: %s", state.HostID, state.ID)
	}
	return conn.Delete(servicepath(state.ServiceID, state.ID))
}

func StopServiceInstance(conn client.Connection, hostID, stateID string) error {
	hpath := hostpath(hostID, stateID)
	var hs HostState
	if err := conn.Get(hpath, &hs); err != nil {
		return err
	}
	glog.V(2).Infof("Stopping instance %s via host %s", stateID, hostID)
	hs.DesiredState = service.SVCStop
	return conn.Set(hpath, &hs)
}
<|endoftext|>


<commit_before>package pingdom

import (
	"fmt"
	"log"
	"strconv"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/russellcardullo/pingdom"
)

func resourcePingdomCheck() *schema.Resource {
	return &schema.Resource{
		Create: resourcePingdomCheckCreate,
		Read:   resourcePingdomCheckRead,
		Update: resourcePingdomCheckUpdate,
		Delete: resourcePingdomCheckDelete,

		Schema: map[string]*schema.Schema{
			"id": &schema.Schema{
				Type:     schema.TypeInt,
				Computed: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"host": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},
		},
	}
}

func resourcePingdomCheckCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	name := d.Get("name").(string)
	host := d.Get("host").(string)
	check := pingdom.HttpCheck{Name: name, Host: host}

	log.Printf("[DEBUG] Check create configuration: %#v, %#v", name, host)

	ck, err := client.CreateCheck(check)
	if err != nil {
		return err
	}

	d.SetId(strconv.Itoa(ck.ID))
	d.Set("hostname", ck.Hostname)
	d.Set("name", ck.Name)

	return nil
}

func resourcePingdomCheckRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}
	ck, err := client.ReadCheck(id)
	if err != nil {
		return fmt.Errorf("Error retrieving check: %s", err)
	}

	d.Set("hostname", ck.Hostname)
	d.Set("name", ck.Name)

	return nil
}

func resourcePingdomCheckUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}

	name := d.Get("name").(string)
	host := d.Get("host").(string)
	check := pingdom.HttpCheck{Name: name, Host: host}

	log.Printf("[DEBUG] Check update configuration: %#v, %#v", name, host)

	_, err = client.UpdateCheck(id, check)
	if err != nil {
		return fmt.Errorf("Error updating check: %s", err)
	}

	return nil
}

func resourcePingdomCheckDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}

	log.Printf("[INFO] Deleting Check: %v", id)

	_, err = client.DeleteCheck(id)
	if err != nil {
		return fmt.Errorf("Error deleting check: %s", err)
	}

	return nil
}
<commit_msg>Add additional parameters supported by pingdom<commit_after>package pingdom

import (
	"fmt"
	"log"
	"strconv"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/russellcardullo/pingdom"
)

func resourcePingdomCheck() *schema.Resource {
	return &schema.Resource{
		Create: resourcePingdomCheckCreate,
		Read:   resourcePingdomCheckRead,
		Update: resourcePingdomCheckUpdate,
		Delete: resourcePingdomCheckDelete,

		Schema: map[string]*schema.Schema{
			"id": &schema.Schema{
				Type:     schema.TypeInt,
				Computed: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"host": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"resolution": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: false,
			},

			"sendtoemail": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},

			"sendtosms": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},

			"sendtotwitter": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},

			"sendtoiphone": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},

			"sendtoandroid": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},

			"sendnotificationwhendown": &schema.Schema{
				Type:     schema.TypeInt,
				Required: false,
				ForceNew: false,
			},

			"notifyagainevery": &schema.Schema{
				Type:     schema.TypeInt,
				Required: false,
				ForceNew: false,
			},

			"notifywhenbackup": &schema.Schema{
				Type:     schema.TypeBool,
				Required: false,
				ForceNew: false,
			},
		},
	}
}

func checkForResource(d *schema.ResourceData) *pingdom.Check {
	check := &pingdom.Check{}
	// required
	if v, ok := d.GetOk("name"); ok {
		check.Name = v.(string)
	}
	if v, ok := d.GetOk("host"); ok {
		check.Hostname = v.(string)
	}

	if v, ok := d.GetOk("resolution"); ok {
		check.Resolution = v.(int)
	}

	// optional
	if v, ok := d.GetOk("sendtoemail"); ok {
		check.SendToEmail = v.(bool)
	}

	if v, ok := d.GetOk("sendtosms"); ok {
		check.SendToSms = v.(bool)
	}

	if v, ok := d.GetOk("sendtoiphone"); ok {
		check.SendToIPhone = v.(bool)
	}

	if v, ok := d.GetOk("sendtoandroid"); ok {
		check.SendToAndroid = v.(bool)
	}

	if v, ok := d.GetOk("sendnotificationwhendown"); ok {
		check.SendNotificationWhenDown = v.(int)
	}

	if v, ok := d.GetOk("notifyagainevery"); ok {
		check.NotifyAgainEvery = v.(int)
	}

	if v, ok := d.GetOk("notifywhenbackup"); ok {
		check.NotifyWhenBackup = v.(bool)
	}
	return check
}

func resourcePingdomCheckCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	check := checkForResource(d)
	log.Printf("[DEBUG] Check create configuration: %#v, %#v", check.Name, check.Hostname)

	ck, err := client.Checks.Create(check)
	if err != nil {
		return err
	}

	d.SetId(strconv.Itoa(ck.ID))
	d.Set("hostname", ck.Hostname)
	d.Set("name", ck.Name)

	return nil
}

func resourcePingdomCheckRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}
	ck, err := client.Checks.Read(id)
	if err != nil {
		return fmt.Errorf("Error retrieving check: %s", err)
	}

	d.Set("hostname", ck.Hostname)
	d.Set("name", ck.Name)
	d.Set("resolution", ck.Resolution)

	return nil
}

func resourcePingdomCheckUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}

	check := checkForResource(d)
	log.Printf("[DEBUG] Check update configuration: %#v, %#v", check.Name, check.Hostname)

	_, err = client.Checks.Update(id, check)
	if err != nil {
		return fmt.Errorf("Error updating check: %s", err)
	}

	return nil
}

func resourcePingdomCheckDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*pingdom.Client)

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving id for resource: %s", err)
	}

	log.Printf("[INFO] Deleting Check: %v", id)

	_, err = client.Checks.Delete(id)
	if err != nil {
		return fmt.Errorf("Error deleting check: %s", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package cloudca

import (
   "fmt"
   "github.com/cloud-ca/go-cloudca"
   "github.com/cloud-ca/go-cloudca/api"
   "github.com/cloud-ca/go-cloudca/services/cloudca"
   "github.com/hashicorp/terraform/helper/schema"
   "strconv"
)

func resourceCloudcaLoadBalancerRule() *schema.Resource {
   return &schema.Resource{
      Create: createLbr,
      Read:   readLbr,
      Delete: deleteLbr,
      Update: updateLbr,

      Schema: map[string]*schema.Schema{
         "service_code": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "A cloudca service code",
         },
         "environment_name": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "Name of environment where load balancer rule should be created",
         },
         "name": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            Description: "Name of the load balancer rule",
         },
         "public_ip_id": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "The public IP to which the rule should be applied",
         },
         "public_ip": &schema.Schema{
            Type:        schema.TypeString,
            Computed:    true,
         },
         "network_id": &schema.Schema{
            Type:        schema.TypeString,
            Optional:    true,
            ForceNew:    true,
            Computed: true,
            Description: "The network ID to bind to",
         },
         "protocol": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "The protocol that this rule should use (eg. TCP, UDP)",
         },
         "algorithm": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            Description: "The algorithm used to load balance",
         },
         "public_port": &schema.Schema{
            Type:        schema.TypeInt,
            Required:    true,
            ForceNew:    true,
            Description: "The port on the public IP",
         },
         "private_port": &schema.Schema{
            Type:        schema.TypeInt,
            Required:    true,
            ForceNew:    true,
            Description: "The port to which the traffic will be load balanced internally",
         },
         "instance_ids": &schema.Schema{
            Type:        schema.TypeList,
            Optional:    true,
            Description: "List of instance ids that will be load balanced",
            Elem:        &schema.Schema{Type: schema.TypeString},
         },
         "stickiness_method": &schema.Schema{
            Type:        schema.TypeString,
            Optional:    true,
            Description: "The stickiness method",
         },
         "stickiness_params": &schema.Schema{
            Type:        schema.TypeMap,
            Optional:    true,
            Description: "The stickiness policy parameters",
         },
      },
   }
}

func createLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, err := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if err != nil {
      return err
   }

   lbr := cloudca.LoadBalancerRule{
      Name:             d.Get("name").(string),
      PublicIpId:       d.Get("public_ip_id").(string),
      NetworkId:        d.Get("network_id").(string),
      Protocol:         d.Get("protocol").(string),
      Algorithm:        d.Get("algorithm").(string),
      PublicPort:       strconv.Itoa(d.Get("public_port").(int)),
      PrivatePort:      strconv.Itoa(d.Get("private_port").(int)),
   }

   _, instanceIdsPresent := d.GetOk("instance_ids")

   if instanceIdsPresent {
      var instanceIds []string
      for _, id := range d.Get("instance_ids").([]interface{}) {
         instanceIds = append(instanceIds, id.(string))
      }
      lbr.InstanceIds = instanceIds
   }

   newLbr, err := ccaResources.LoadBalancerRules.Create(lbr)
   if err != nil {
      return err
   }

   d.SetId(newLbr.Id)
   return readLbr(d, meta)
}

func readLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, _ := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   lbr, err := ccaResources.LoadBalancerRules.Get(d.Id())
   if err != nil {
      return handleLbrNotFoundError(err, d)
   }

   d.Set("name", lbr.Name)
   d.Set("public_ip_id", lbr.PublicIpId)
   d.Set("network_id", lbr.NetworkId)
   d.Set("instance_ids", lbr.InstanceIds)
   d.Set("algorithm", lbr.Algorithm)
   d.Set("protocol", lbr.Protocol)
   d.Set("public_port", lbr.PublicPort)
   d.Set("private_port", lbr.PrivatePort)
   d.Set("public_ip", lbr.PublicIp)

   return nil
}

func deleteLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, _ := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if _, err := ccaResources.LoadBalancerRules.Delete(d.Id()); err != nil {
      return handleLbrNotFoundError(err, d)
   }
   return nil
}

func updateLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, err := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if err != nil {
      return err
   }

   d.Partial(true)

   if d.HasChange("name") || d.HasChange("algorithm") {
      newName := d.Get("name").(string)
      newAlgorithm := d.Get("algorithm").(string)
      _, err := ccaResources.LoadBalancerRules.Update(cloudca.LoadBalancerRule{Id: d.Id(), Name: newName, Algorithm: newAlgorithm})
      if err != nil {
         return err
      }
   }

   if d.HasChange("instance_ids") {

      _, aclErr := ccaResources.Tiers.ChangeAcl(d.Id(), d.Get("network_acl_id").(string))
      if aclErr != nil {
         return aclErr
      }
   }

   d.Partial(false)

   // lbr := cloudca.LoadBalancerRule{
   //    Name:             d.Get("name").(string),
   //    PublicIpId:       d.Get("public_ip_id").(string),
   //    NetworkId:        d.Get("network_id").(string),
   //    Protocol:         d.Get("protocol").(string),
   //    Algorithm:        d.Get("algorithm").(string),
   //    PublicPort:       strconv.Itoa(d.Get("public_port").(int)),
   //    PrivatePort:      strconv.Itoa(d.Get("private_port").(int)),
   // }

   // _, instanceIdsPresent := d.GetOk("instance_ids")

   // if instanceIdsPresent {
   //    var instanceIds []string
   //    for _, id := range d.Get("instance_ids").([]interface{}) {
   //       instanceIds = append(instanceIds, id.(string))
   //    }
   //    lbr.InstanceIds = instanceIds
   // }

   // newLbr, err := ccaResources.LoadBalancerRules.Create(lbr)
   // if err != nil {
   //    return err
   // }

   // d.SetId(newLbr.Id)
   return readLbr(d, meta)
}

func handleLbrNotFoundError(err error, d *schema.ResourceData) error {
   if ccaError, ok := err.(api.CcaErrorResponse); ok {
      if ccaError.StatusCode == 404 {
         fmt.Errorf("Load balancer rule with id %s was not found", d.Id())
         d.SetId("")
         return err
      }
   }

   return err
}

<commit_msg>Added update instances. Adding update Stickiness Method + parameters<commit_after>package cloudca

import (
   "fmt"
   "github.com/cloud-ca/go-cloudca"
   "github.com/cloud-ca/go-cloudca/api"
   "github.com/cloud-ca/go-cloudca/services/cloudca"
   "github.com/hashicorp/terraform/helper/schema"
   "strconv"
)

func resourceCloudcaLoadBalancerRule() *schema.Resource {
   return &schema.Resource{
      Create: createLbr,
      Read:   readLbr,
      Delete: deleteLbr,
      Update: updateLbr,

      Schema: map[string]*schema.Schema{
         "service_code": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "A cloudca service code",
         },
         "environment_name": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "Name of environment where load balancer rule should be created",
         },
         "name": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            Description: "Name of the load balancer rule",
         },
         "public_ip_id": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "The public IP to which the rule should be applied",
         },
         "public_ip": &schema.Schema{
            Type:        schema.TypeString,
            Computed:    true,
         },
         "network_id": &schema.Schema{
            Type:        schema.TypeString,
            Optional:    true,
            ForceNew:    true,
            Computed: true,
            Description: "The network ID to bind to",
         },
         "protocol": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            ForceNew:    true,
            Description: "The protocol that this rule should use (eg. TCP, UDP)",
         },
         "algorithm": &schema.Schema{
            Type:        schema.TypeString,
            Required:    true,
            Description: "The algorithm used to load balance",
         },
         "public_port": &schema.Schema{
            Type:        schema.TypeInt,
            Required:    true,
            ForceNew:    true,
            Description: "The port on the public IP",
         },
         "private_port": &schema.Schema{
            Type:        schema.TypeInt,
            Required:    true,
            ForceNew:    true,
            Description: "The port to which the traffic will be load balanced internally",
         },
         "instance_ids": &schema.Schema{
            Type:        schema.TypeList,
            Optional:    true,
            Description: "List of instance ids that will be load balanced",
            Elem:        &schema.Schema{Type: schema.TypeString},
         },
         "stickiness_method": &schema.Schema{
            Type:        schema.TypeString,
            Optional:    true,
            Description: "The stickiness method",
         },
         "stickiness_params": &schema.Schema{
            Type:        schema.TypeMap,
            Optional:    true,
            Description: "The stickiness policy parameters",
         },
      },
   }
}

func createLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, err := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if err != nil {
      return err
   }

   lbr := cloudca.LoadBalancerRule{
      Name:             d.Get("name").(string),
      PublicIpId:       d.Get("public_ip_id").(string),
      NetworkId:        d.Get("network_id").(string),
      Protocol:         d.Get("protocol").(string),
      Algorithm:        d.Get("algorithm").(string),
      PublicPort:       strconv.Itoa(d.Get("public_port").(int)),
      PrivatePort:      strconv.Itoa(d.Get("private_port").(int)),
   }

   _, instanceIdsPresent := d.GetOk("instance_ids")

   if instanceIdsPresent {
      var instanceIds []string
      for _, id := range d.Get("instance_ids").([]interface{}) {
         instanceIds = append(instanceIds, id.(string))
      }
      lbr.InstanceIds = instanceIds
   }

   stickinessMethod, stickinessMethodPresent := d.GetOk("stickiness_method")
   if stickinessMethodPresent {
      lbr.StickinessMethod = stickinessMethod.(string)
   }

   stickinessParams, stickinessPolicyParamsPresent := d.GetOk("stickiness_params")
   if stickinessPolicyParamsPresent {
      var stickinessPolicyParameters = make(map[string]string)
      for k, v := range stickinessParams.(map[string]interface{}) {
         stickinessPolicyParameters[k] = v.(string)
      }
      lbr.StickinessPolicyParameters = stickinessPolicyParameters;
   }

   newLbr, err := ccaResources.LoadBalancerRules.Create(lbr)
   if err != nil {
      return err
   }

   d.SetId(newLbr.Id)
   return readLbr(d, meta)
}

func readLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, _ := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   lbr, err := ccaResources.LoadBalancerRules.Get(d.Id())
   if err != nil {
      return handleLbrNotFoundError(err, d)
   }

   d.Set("name", lbr.Name)
   d.Set("public_ip_id", lbr.PublicIpId)
   d.Set("network_id", lbr.NetworkId)
   d.Set("instance_ids", lbr.InstanceIds)
   d.Set("algorithm", lbr.Algorithm)
   d.Set("protocol", lbr.Protocol)
   d.Set("public_port", lbr.PublicPort)
   d.Set("private_port", lbr.PrivatePort)
   d.Set("public_ip", lbr.PublicIp)
   d.Set("stickiness_method", lbr.StickinessMethod)
   d.Set("stickiness_params", lbr.StickinessPolicyParameters)

   return nil
}

func deleteLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, _ := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if _, err := ccaResources.LoadBalancerRules.Delete(d.Id()); err != nil {
      return handleLbrNotFoundError(err, d)
   }
   return nil
}

func updateLbr(d *schema.ResourceData, meta interface{}) error {
   client := meta.(*cca.CcaClient)
   resources, err := client.GetResources(d.Get("service_code").(string), d.Get("environment_name").(string))
   ccaResources := resources.(cloudca.Resources)

   if err != nil {
      return err
   }

   d.Partial(true)

   if d.HasChange("name") || d.HasChange("algorithm") {
      newName := d.Get("name").(string)
      newAlgorithm := d.Get("algorithm").(string)
      _, err := ccaResources.LoadBalancerRules.Update(cloudca.LoadBalancerRule{Id: d.Id(), Name: newName, Algorithm: newAlgorithm})
      if err != nil {
         return err
      }
   }

   if d.HasChange("instance_ids") {
      var instanceIds []string
      for _, id := range d.Get("instance_ids").([]interface{}) {
         instanceIds = append(instanceIds, id.(string))
      }

      _, instanceErr := ccaResources.LoadBalancerRules.SetLoadBalancerRuleInstances(d.Id(), instanceIds)
      if instanceErr != nil {
         return instanceErr
      }
   }

   if d.HasChange("instance_ids") {
      var instanceIds []string
      for _, id := range d.Get("instance_ids").([]interface{}) {
         instanceIds = append(instanceIds, id.(string))
      }

      _, instanceErr := ccaResources.LoadBalancerRules.SetLoadBalancerRuleInstances(d.Id(), instanceIds)
      if instanceErr != nil {
         return instanceErr
      }
   }

   d.Partial(false)

   // lbr := cloudca.LoadBalancerRule{
   //    Name:             d.Get("name").(string),
   //    PublicIpId:       d.Get("public_ip_id").(string),
   //    NetworkId:        d.Get("network_id").(string),
   //    Protocol:         d.Get("protocol").(string),
   //    Algorithm:        d.Get("algorithm").(string),
   //    PublicPort:       strconv.Itoa(d.Get("public_port").(int)),
   //    PrivatePort:      strconv.Itoa(d.Get("private_port").(int)),
   // }

   // _, instanceIdsPresent := d.GetOk("instance_ids")

   // if instanceIdsPresent {
   //    var instanceIds []string
   //    for _, id := range d.Get("instance_ids").([]interface{}) {
   //       instanceIds = append(instanceIds, id.(string))
   //    }
   //    lbr.InstanceIds = instanceIds

   // }

   // newLbr, err := ccaResources.LoadBalancerRules.Create(lbr)
   // if err != nil {
   //    return err
   // }

   // d.SetId(newLbr.Id)
   return readLbr(d, meta)
}

func handleLbrNotFoundError(err error, d *schema.ResourceData) error {
   if ccaError, ok := err.(api.CcaErrorResponse); ok {
      if ccaError.StatusCode == 404 {
         fmt.Errorf("Load balancer rule with id %s was not found", d.Id())
         d.SetId("")
         return err
      }
   }

   return err
}

<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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

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

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

package handlers

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path"
	"strings"
	"time"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/protocol"
	"camlistore.org/pkg/httputil"
	"camlistore.org/pkg/jsonsign/signhandler"
	"camlistore.org/pkg/readerutil"
	"camlistore.org/pkg/schema"
)

// CreateBatchUploadHandler returns the handler that receives multi-part form uploads
// to upload many blobs at once. See doc/protocol/blob-upload-protocol.txt.
func CreateBatchUploadHandler(storage blobserver.BlobReceiveConfiger) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		handleMultiPartUpload(rw, req, storage)
	})
}

// CreatePutUploadHandler returns the handler that receives a single
// blob at the blob's final URL, via the PUT method.  See
// doc/protocol/blob-upload-protocol.txt.
func CreatePutUploadHandler(storage blobserver.BlobReceiver) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != "PUT" {
			log.Printf("Inconfigured upload handler.")
			httputil.BadRequestError(rw, "Inconfigured handler.")
			return
		}
		// For non-chunked uploads, we catch it here. For chunked uploads, it's caught
		// by blobserver.Receive's LimitReader.
		if req.ContentLength > blobserver.MaxBlobSize {
			httputil.BadRequestError(rw, "blob too big")
			return
		}
		blobrefStr := path.Base(req.URL.Path)
		br, ok := blob.Parse(blobrefStr)
		if !ok {
			log.Printf("Invalid PUT request to %q", req.URL.Path)
			httputil.BadRequestError(rw, "Bad path")
			return
		}
		if !br.IsSupported() {
			httputil.BadRequestError(rw, "unsupported object hash function")
			return
		}
		_, err := blobserver.Receive(storage, br, req.Body)
		if err == blobserver.ErrCorruptBlob {
			httputil.BadRequestError(rw, "data doesn't match declared digest")
			return
		}
		if err != nil {
			httputil.ServeError(rw, req, err)
			return
		}
		rw.WriteHeader(http.StatusNoContent)
	})
}

// vivify verifies that all the chunks for the file described by fileblob are on the blobserver.
// It makes a planned permanode, signs it, and uploads it. It finally makes a camliContent claim
// on that permanode for fileblob, signs it, and uploads it to the blobserver.
func vivify(blobReceiver blobserver.BlobReceiveConfiger, fileblob blob.SizedRef) error {
	sf, ok := blobReceiver.(blob.StreamingFetcher)
	if !ok {
		return fmt.Errorf("BlobReceiver is not a StreamingFetcher")
	}
	fetcher := blob.SeekerFromStreamingFetcher(sf)
	fr, err := schema.NewFileReader(fetcher, fileblob.Ref)
	if err != nil {
		return fmt.Errorf("Filereader error for blobref %v: %v", fileblob.Ref.String(), err)
	}
	defer fr.Close()

	h := sha1.New()
	n, err := io.Copy(h, fr)
	if err != nil {
		return fmt.Errorf("Could not read all file of blobref %v: %v", fileblob.Ref.String(), err)
	}
	if n != fr.Size() {
		return fmt.Errorf("Could not read all file of blobref %v. Wanted %v, got %v", fileblob.Ref.String(), fr.Size(), n)
	}

	config := blobReceiver.Config()
	if config == nil {
		return errors.New("blobReceiver has no config")
	}
	hf := config.HandlerFinder
	if hf == nil {
		return errors.New("blobReceiver config has no HandlerFinder")
	}
	JSONSignRoot, sh, err := hf.FindHandlerByType("jsonsign")
	if err != nil || sh == nil {
		return errors.New("jsonsign handler not found")
	}
	sigHelper, ok := sh.(*signhandler.Handler)
	if !ok {
		return errors.New("handler is not a JSON signhandler")
	}
	discoMap := sigHelper.DiscoveryMap(JSONSignRoot)
	publicKeyBlobRef, ok := discoMap["publicKeyBlobRef"].(string)
	if !ok {
		return fmt.Errorf("Discovery: json decoding error: %v", err)
	}

	// The file schema must have a modtime to vivify, as the modtime is used for all three of:
	// 1) the permanode's signature
	// 2) the camliContent attribute claim's "claimDate"
	// 3) the signature time of 2)
	claimDate, err := time.Parse(time.RFC3339, fr.FileSchema().UnixMtime)
	if err != nil {
		return fmt.Errorf("While parsing modtime for file %v: %v", fr.FileSchema().FileName, err)
	}

	permanodeBB := schema.NewHashPlannedPermanode(h)
	permanodeBB.SetSigner(blob.MustParse(publicKeyBlobRef))
	permanodeBB.SetClaimDate(claimDate)
	permanodeSigned, err := sigHelper.Sign(permanodeBB)
	if err != nil {
		return fmt.Errorf("Signing permanode %v: %v", permanodeSigned, err)
	}
	permanodeRef := blob.SHA1FromString(permanodeSigned)
	_, err = blobserver.ReceiveNoHash(blobReceiver, permanodeRef, strings.NewReader(permanodeSigned))
	if err != nil {
		return fmt.Errorf("While uploading signed permanode %v, %v: %v", permanodeRef, permanodeSigned, err)
	}

	contentClaimBB := schema.NewSetAttributeClaim(permanodeRef, "camliContent", fileblob.Ref.String())
	contentClaimBB.SetSigner(blob.MustParse(publicKeyBlobRef))
	contentClaimBB.SetClaimDate(claimDate)
	contentClaimSigned, err := sigHelper.Sign(contentClaimBB)
	if err != nil {
		return fmt.Errorf("Signing camliContent claim: %v", err)
	}
	contentClaimRef := blob.SHA1FromString(contentClaimSigned)
	_, err = blobserver.ReceiveNoHash(blobReceiver, contentClaimRef, strings.NewReader(contentClaimSigned))
	if err != nil {
		return fmt.Errorf("While uploading signed camliContent claim %v, %v: %v", contentClaimRef, contentClaimSigned, err)
	}
	return nil
}

func handleMultiPartUpload(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) {
	res := new(protocol.UploadResponse)

	if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) {
		log.Printf("Inconfigured handler upload handler")
		httputil.BadRequestError(conn, "Inconfigured handler.")
		return
	}

	receivedBlobs := make([]blob.SizedRef, 0, 10)

	multipart, err := req.MultipartReader()
	if multipart == nil {
		httputil.BadRequestError(conn, fmt.Sprintf(
			"Expected multipart/form-data POST request; %v", err))
		return
	}

	var errBuf bytes.Buffer
	addError := func(s string) {
		log.Printf("Client error: %s", s)
		if errBuf.Len() > 0 {
			errBuf.WriteByte('\n')
		}
		errBuf.WriteString(s)
	}

	for {
		mimePart, err := multipart.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			addError(fmt.Sprintf("Error reading multipart section: %v", err))
			break
		}

		contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition"))
		if err != nil {
			addError("invalid Content-Disposition")
			break
		}

		if contentDisposition != "form-data" {
			addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition))
			break
		}

		formName := params["name"]
		ref, ok := blob.Parse(formName)
		if !ok {
			addError(fmt.Sprintf("Ignoring form key %q", formName))
			continue
		}

		var tooBig int64 = blobserver.MaxBlobSize + 1
		var readBytes int64
		blobGot, err := blobserver.Receive(blobReceiver, ref, &readerutil.CountingReader{
			io.LimitReader(mimePart, tooBig),
			&readBytes,
		})
		if readBytes == tooBig {
			err = fmt.Errorf("blob over the limit of %d bytes", blobserver.MaxBlobSize)
		}
		if err != nil {
			addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err))
			break
		}
		log.Printf("Received blob %v\n", blobGot)
		receivedBlobs = append(receivedBlobs, blobGot)
	}

	for _, got := range receivedBlobs {
		res.Received = append(res.Received, &protocol.RefAndSize{
			Ref:  got.Ref,
			Size: uint32(got.Size),
		})
	}

	if req.Header.Get("X-Camlistore-Vivify") == "1" {
		for _, got := range receivedBlobs {
			err := vivify(blobReceiver, got)
			if err != nil {
				addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.Ref.String(), err))
			} else {
				conn.Header().Add("X-Camlistore-Vivified", got.Ref.String())
			}
		}
	}

	res.ErrorText = errBuf.String()

	httputil.ReturnJSON(conn, res)
}
<commit_msg>Rename ResponseWriter 'conn' to 'rw'<commit_after>/*
Copyright 2011 Google Inc.

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

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

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

package handlers

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path"
	"strings"
	"time"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/blobserver/protocol"
	"camlistore.org/pkg/httputil"
	"camlistore.org/pkg/jsonsign/signhandler"
	"camlistore.org/pkg/readerutil"
	"camlistore.org/pkg/schema"
)

// CreateBatchUploadHandler returns the handler that receives multi-part form uploads
// to upload many blobs at once. See doc/protocol/blob-upload-protocol.txt.
func CreateBatchUploadHandler(storage blobserver.BlobReceiveConfiger) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		handleMultiPartUpload(rw, req, storage)
	})
}

// CreatePutUploadHandler returns the handler that receives a single
// blob at the blob's final URL, via the PUT method.  See
// doc/protocol/blob-upload-protocol.txt.
func CreatePutUploadHandler(storage blobserver.BlobReceiver) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != "PUT" {
			log.Printf("Inconfigured upload handler.")
			httputil.BadRequestError(rw, "Inconfigured handler.")
			return
		}
		// For non-chunked uploads, we catch it here. For chunked uploads, it's caught
		// by blobserver.Receive's LimitReader.
		if req.ContentLength > blobserver.MaxBlobSize {
			httputil.BadRequestError(rw, "blob too big")
			return
		}
		blobrefStr := path.Base(req.URL.Path)
		br, ok := blob.Parse(blobrefStr)
		if !ok {
			log.Printf("Invalid PUT request to %q", req.URL.Path)
			httputil.BadRequestError(rw, "Bad path")
			return
		}
		if !br.IsSupported() {
			httputil.BadRequestError(rw, "unsupported object hash function")
			return
		}
		_, err := blobserver.Receive(storage, br, req.Body)
		if err == blobserver.ErrCorruptBlob {
			httputil.BadRequestError(rw, "data doesn't match declared digest")
			return
		}
		if err != nil {
			httputil.ServeError(rw, req, err)
			return
		}
		rw.WriteHeader(http.StatusNoContent)
	})
}

// vivify verifies that all the chunks for the file described by fileblob are on the blobserver.
// It makes a planned permanode, signs it, and uploads it. It finally makes a camliContent claim
// on that permanode for fileblob, signs it, and uploads it to the blobserver.
func vivify(blobReceiver blobserver.BlobReceiveConfiger, fileblob blob.SizedRef) error {
	sf, ok := blobReceiver.(blob.StreamingFetcher)
	if !ok {
		return fmt.Errorf("BlobReceiver is not a StreamingFetcher")
	}
	fetcher := blob.SeekerFromStreamingFetcher(sf)
	fr, err := schema.NewFileReader(fetcher, fileblob.Ref)
	if err != nil {
		return fmt.Errorf("Filereader error for blobref %v: %v", fileblob.Ref.String(), err)
	}
	defer fr.Close()

	h := sha1.New()
	n, err := io.Copy(h, fr)
	if err != nil {
		return fmt.Errorf("Could not read all file of blobref %v: %v", fileblob.Ref.String(), err)
	}
	if n != fr.Size() {
		return fmt.Errorf("Could not read all file of blobref %v. Wanted %v, got %v", fileblob.Ref.String(), fr.Size(), n)
	}

	config := blobReceiver.Config()
	if config == nil {
		return errors.New("blobReceiver has no config")
	}
	hf := config.HandlerFinder
	if hf == nil {
		return errors.New("blobReceiver config has no HandlerFinder")
	}
	JSONSignRoot, sh, err := hf.FindHandlerByType("jsonsign")
	if err != nil || sh == nil {
		return errors.New("jsonsign handler not found")
	}
	sigHelper, ok := sh.(*signhandler.Handler)
	if !ok {
		return errors.New("handler is not a JSON signhandler")
	}
	discoMap := sigHelper.DiscoveryMap(JSONSignRoot)
	publicKeyBlobRef, ok := discoMap["publicKeyBlobRef"].(string)
	if !ok {
		return fmt.Errorf("Discovery: json decoding error: %v", err)
	}

	// The file schema must have a modtime to vivify, as the modtime is used for all three of:
	// 1) the permanode's signature
	// 2) the camliContent attribute claim's "claimDate"
	// 3) the signature time of 2)
	claimDate, err := time.Parse(time.RFC3339, fr.FileSchema().UnixMtime)
	if err != nil {
		return fmt.Errorf("While parsing modtime for file %v: %v", fr.FileSchema().FileName, err)
	}

	permanodeBB := schema.NewHashPlannedPermanode(h)
	permanodeBB.SetSigner(blob.MustParse(publicKeyBlobRef))
	permanodeBB.SetClaimDate(claimDate)
	permanodeSigned, err := sigHelper.Sign(permanodeBB)
	if err != nil {
		return fmt.Errorf("Signing permanode %v: %v", permanodeSigned, err)
	}
	permanodeRef := blob.SHA1FromString(permanodeSigned)
	_, err = blobserver.ReceiveNoHash(blobReceiver, permanodeRef, strings.NewReader(permanodeSigned))
	if err != nil {
		return fmt.Errorf("While uploading signed permanode %v, %v: %v", permanodeRef, permanodeSigned, err)
	}

	contentClaimBB := schema.NewSetAttributeClaim(permanodeRef, "camliContent", fileblob.Ref.String())
	contentClaimBB.SetSigner(blob.MustParse(publicKeyBlobRef))
	contentClaimBB.SetClaimDate(claimDate)
	contentClaimSigned, err := sigHelper.Sign(contentClaimBB)
	if err != nil {
		return fmt.Errorf("Signing camliContent claim: %v", err)
	}
	contentClaimRef := blob.SHA1FromString(contentClaimSigned)
	_, err = blobserver.ReceiveNoHash(blobReceiver, contentClaimRef, strings.NewReader(contentClaimSigned))
	if err != nil {
		return fmt.Errorf("While uploading signed camliContent claim %v, %v: %v", contentClaimRef, contentClaimSigned, err)
	}
	return nil
}

func handleMultiPartUpload(rw http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) {
	res := new(protocol.UploadResponse)

	if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) {
		log.Printf("Inconfigured handler upload handler")
		httputil.BadRequestError(rw, "Inconfigured handler.")
		return
	}

	receivedBlobs := make([]blob.SizedRef, 0, 10)

	multipart, err := req.MultipartReader()
	if multipart == nil {
		httputil.BadRequestError(rw, fmt.Sprintf(
			"Expected multipart/form-data POST request; %v", err))
		return
	}

	var errBuf bytes.Buffer
	addError := func(s string) {
		log.Printf("Client error: %s", s)
		if errBuf.Len() > 0 {
			errBuf.WriteByte('\n')
		}
		errBuf.WriteString(s)
	}

	for {
		mimePart, err := multipart.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			addError(fmt.Sprintf("Error reading multipart section: %v", err))
			break
		}

		contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition"))
		if err != nil {
			addError("invalid Content-Disposition")
			break
		}

		if contentDisposition != "form-data" {
			addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition))
			break
		}

		formName := params["name"]
		ref, ok := blob.Parse(formName)
		if !ok {
			addError(fmt.Sprintf("Ignoring form key %q", formName))
			continue
		}

		var tooBig int64 = blobserver.MaxBlobSize + 1
		var readBytes int64
		blobGot, err := blobserver.Receive(blobReceiver, ref, &readerutil.CountingReader{
			io.LimitReader(mimePart, tooBig),
			&readBytes,
		})
		if readBytes == tooBig {
			err = fmt.Errorf("blob over the limit of %d bytes", blobserver.MaxBlobSize)
		}
		if err != nil {
			addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err))
			break
		}
		log.Printf("Received blob %v\n", blobGot)
		receivedBlobs = append(receivedBlobs, blobGot)
	}

	for _, got := range receivedBlobs {
		res.Received = append(res.Received, &protocol.RefAndSize{
			Ref:  got.Ref,
			Size: uint32(got.Size),
		})
	}

	if req.Header.Get("X-Camlistore-Vivify") == "1" {
		for _, got := range receivedBlobs {
			err := vivify(blobReceiver, got)
			if err != nil {
				addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.Ref.String(), err))
			} else {
				rw.Header().Add("X-Camlistore-Vivified", got.Ref.String())
			}
		}
	}

	res.ErrorText = errBuf.String()

	httputil.ReturnJSON(rw, res)
}
<|endoftext|>


<commit_before>package emulator

import (
	"testing"
	"k8s.io/kubernetes/pkg/client/cache"
	apitesting "k8s.io/kubernetes/pkg/api/testing"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/watch"
	"time"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/api"
	"reflect"
	"fmt"
)

func newTestRestClient() *RESTClient {
	return &RESTClient{
		NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
	}
}

func getResourceWatcher(client cache.Getter, resource string) watch.Interface {
	// client listerWatcher
	listerWatcher := cache.NewListWatchFromClient(client, resource, api.NamespaceAll, fields.ParseSelectorOrDie(""))
	// ask for watcher data
	timemoutseconds := int64(10)

	options := api.ListOptions{
		ResourceVersion: "0",
		// We want to avoid situations of hanging watchers. Stop any wachers that do not
		// receive any events within the timeout window.
		TimeoutSeconds: &timemoutseconds,
	}

	w, _ := listerWatcher.Watch(options)
	return w
}

func emitEvent(client *RESTClient, resource string, test eventTest) {
	switch resource {
		case "pods":
			client.EmitPodWatchEvent(test.event, test.item.(*api.Pod))
		case "services":
			client.EmitServiceWatchEvent(test.event, test.item.(*api.Service))
		case "replicationcontrollers":
			client.EmitReplicationControllerWatchEvent(test.event, test.item.(*api.ReplicationController))
		default:
			fmt.Printf("Unsupported resource %s", resource)
			// TODO(jchaloup): log the error
	}
}

type eventTest struct{
	event watch.EventType
	item interface{}
}

func testWatch(tests []eventTest, resource string, t *testing.T) {

	client := newTestRestClient()
	w := getResourceWatcher(client, resource)

	t.Logf("Emitting first two events")
	emitEvent(client, resource, tests[0])
	emitEvent(client, resource, tests[1])
	// wait for a while so both events are in one byte stream
	time.Sleep(time.Second)
	sync := make(chan struct{})

	// retrieve all events one by one in the same order
	go func() {
		for _, test := range tests {
			t.Logf("Waiting for event")
			event, ok := <-w.ResultChan()
			if !ok {
				t.Errorf("Unexpected watch close")
			}
			t.Logf("Event received")
			if event.Type != test.event {
				t.Errorf("Expected event type %q, got %q", test.event, event.Type)
			}
			if !reflect.DeepEqual(test.item, event.Object) {
				t.Errorf("unexpected object: expected: %#v\n actual: %#v", test.item, event.Object)
			}
		}
		sync<- struct{}{}
	}()

	// send remaining events
	t.Logf("Emitting remaining events")
	for _, test := range tests[2:] {
		time.Sleep(time.Second)
		emitEvent(client, resource, test)
		t.Logf("Event emitted")
	}

	// wait for all events
	<-sync
	close(sync)
	client.Close()
}

func TestWatchPods(t *testing.T) {

	pod := &api.Pod{
		ObjectMeta: api.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
		Spec:       apitesting.DeepEqualSafePodSpec(),
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: pod,
		},
		{
			event: watch.Added,
			item: pod,
		},
		{
			event: watch.Modified,
			item: pod,
		},
		{
			event: watch.Deleted,
			item: pod,
		},
	}

	testWatch(tests, "pods", t)
}

func TestWatchServices(t *testing.T) {

	service := &api.Service{
		ObjectMeta: api.ObjectMeta{Name: "service1", Namespace: "test", ResourceVersion: "12"},
		Spec: api.ServiceSpec{
			SessionAffinity: "None",
			Type:            api.ServiceTypeClusterIP,
		},
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: service,
		},
		{
			event: watch.Added,
			item: service,
		},
		{
			event: watch.Modified,
			item: service,
		},
		{
			event: watch.Deleted,
			item: service,
		},
	}

	testWatch(tests, "services", t)
}

func TestWatchReplicationControllers(t *testing.T) {

	rc := &api.ReplicationController{
		ObjectMeta: api.ObjectMeta{Name: "replicationcontroller1", Namespace: "test", ResourceVersion: "18"},
		Spec: api.ReplicationControllerSpec{
			Replicas: 1,
		},
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: rc,
		},
		{
			event: watch.Added,
			item: rc,
		},
		{
			event: watch.Modified,
			item: rc,
		},
		{
			event: watch.Deleted,
			item: rc,
		},
	}

	testWatch(tests, "replicationcontrollers", t)
}

<commit_msg>wait less time when testing resource watcher<commit_after>package emulator

import (
	"testing"
	"k8s.io/kubernetes/pkg/client/cache"
	apitesting "k8s.io/kubernetes/pkg/api/testing"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/watch"
	"time"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/api"
	"reflect"
	"fmt"
)

func newTestRestClient() *RESTClient {
	return &RESTClient{
		NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
	}
}

func getResourceWatcher(client cache.Getter, resource string) watch.Interface {
	// client listerWatcher
	listerWatcher := cache.NewListWatchFromClient(client, resource, api.NamespaceAll, fields.ParseSelectorOrDie(""))
	// ask for watcher data
	timemoutseconds := int64(10)

	options := api.ListOptions{
		ResourceVersion: "0",
		// We want to avoid situations of hanging watchers. Stop any wachers that do not
		// receive any events within the timeout window.
		TimeoutSeconds: &timemoutseconds,
	}

	w, _ := listerWatcher.Watch(options)
	return w
}

func emitEvent(client *RESTClient, resource string, test eventTest) {
	switch resource {
		case "pods":
			client.EmitPodWatchEvent(test.event, test.item.(*api.Pod))
		case "services":
			client.EmitServiceWatchEvent(test.event, test.item.(*api.Service))
		case "replicationcontrollers":
			client.EmitReplicationControllerWatchEvent(test.event, test.item.(*api.ReplicationController))
		default:
			fmt.Printf("Unsupported resource %s", resource)
			// TODO(jchaloup): log the error
	}
}

type eventTest struct{
	event watch.EventType
	item interface{}
}

func testWatch(tests []eventTest, resource string, t *testing.T) {

	client := newTestRestClient()
	w := getResourceWatcher(client, resource)

	t.Logf("Emitting first two events")
	emitEvent(client, resource, tests[0])
	emitEvent(client, resource, tests[1])
	// wait for a while so both events are in one byte stream
	time.Sleep(10*time.Millisecond)
	sync := make(chan struct{})

	// retrieve all events one by one in the same order
	go func() {
		for _, test := range tests {
			t.Logf("Waiting for event")
			event, ok := <-w.ResultChan()
			if !ok {
				t.Errorf("Unexpected watch close")
			}
			t.Logf("Event received")
			if event.Type != test.event {
				t.Errorf("Expected event type %q, got %q", test.event, event.Type)
			}
			if !reflect.DeepEqual(test.item, event.Object) {
				t.Errorf("unexpected object: expected: %#v\n actual: %#v", test.item, event.Object)
			}
		}
		sync<- struct{}{}
	}()

	// send remaining events
	t.Logf("Emitting remaining events")
	for _, test := range tests[2:] {
		time.Sleep(10*time.Millisecond)
		emitEvent(client, resource, test)
		t.Logf("Event emitted")
	}

	// wait for all events
	<-sync
	close(sync)
	client.Close()
}

func TestWatchPods(t *testing.T) {

	pod := &api.Pod{
		ObjectMeta: api.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
		Spec:       apitesting.DeepEqualSafePodSpec(),
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: pod,
		},
		{
			event: watch.Added,
			item: pod,
		},
		{
			event: watch.Modified,
			item: pod,
		},
		{
			event: watch.Deleted,
			item: pod,
		},
	}

	testWatch(tests, "pods", t)
}

func TestWatchServices(t *testing.T) {

	service := &api.Service{
		ObjectMeta: api.ObjectMeta{Name: "service1", Namespace: "test", ResourceVersion: "12"},
		Spec: api.ServiceSpec{
			SessionAffinity: "None",
			Type:            api.ServiceTypeClusterIP,
		},
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: service,
		},
		{
			event: watch.Added,
			item: service,
		},
		{
			event: watch.Modified,
			item: service,
		},
		{
			event: watch.Deleted,
			item: service,
		},
	}

	testWatch(tests, "services", t)
}

func TestWatchReplicationControllers(t *testing.T) {

	rc := &api.ReplicationController{
		ObjectMeta: api.ObjectMeta{Name: "replicationcontroller1", Namespace: "test", ResourceVersion: "18"},
		Spec: api.ReplicationControllerSpec{
			Replicas: 1,
		},
	}

	tests := []eventTest{
		{
			event: watch.Modified,
			item: rc,
		},
		{
			event: watch.Added,
			item: rc,
		},
		{
			event: watch.Modified,
			item: rc,
		},
		{
			event: watch.Deleted,
			item: rc,
		},
	}

	testWatch(tests, "replicationcontrollers", t)
}

<|endoftext|>


<commit_before>/*
Copyright 2016 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 collectors

import (
	"testing"
	"time"

	"k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	rs1Replicas int32 = 5
	rs2Replicas int32 = 0
)

func TestReplicaSetCollector(t *testing.T) {
	// Fixed metadata on type and help text. We prepend this to every expected
	// output so we only have to modify a single place when doing adjustments.
	var test = true

	const metadata = `
		# HELP kube_replicaset_created Unix creation timestamp
		# TYPE kube_replicaset_created gauge
	  # HELP kube_replicaset_metadata_generation Sequence number representing a specific generation of the desired state.
		# TYPE kube_replicaset_metadata_generation gauge
		# HELP kube_replicaset_status_replicas The number of replicas per ReplicaSet.
		# TYPE kube_replicaset_status_replicas gauge
		# HELP kube_replicaset_status_fully_labeled_replicas The number of fully labeled replicas per ReplicaSet.
		# TYPE kube_replicaset_status_fully_labeled_replicas gauge
		# HELP kube_replicaset_status_ready_replicas The number of ready replicas per ReplicaSet.
		# TYPE kube_replicaset_status_ready_replicas gauge
		# HELP kube_replicaset_status_observed_generation The generation observed by the ReplicaSet controller.
		# TYPE kube_replicaset_status_observed_generation gauge
		# HELP kube_replicaset_spec_replicas Number of desired pods for a ReplicaSet.
		# TYPE kube_replicaset_spec_replicas gauge
		# HELP kube_replicaset_owner Information about the ReplicaSet's owner.
		# TYPE kube_replicaset_owner gauge
		# HELP kube_replicaset_labels Kubernetes labels converted to Prometheus labels.
		# TYPE kube_replicaset_labels gauge
	`
	cases := []generateMetricsTestCase{
		{
			Obj: &v1beta1.ReplicaSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:              "rs1",
					CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)},
					Namespace:         "ns1",
					Generation:        21,
					OwnerReferences: []metav1.OwnerReference{
						{
							Kind:       "Deployment",
							Name:       "dp-name",
							Controller: &test,
						},
					},
					Labels: map[string]string{
						"app": "example1",
					},
				},
				Status: v1beta1.ReplicaSetStatus{
					Replicas:             5,
					FullyLabeledReplicas: 10,
					ReadyReplicas:        5,
					ObservedGeneration:   1,
				},
				Spec: v1beta1.ReplicaSetSpec{
					Replicas: &rs1Replicas,
				},
			},
			Want: `
				kube_replicaset_labels{replicaset="rs1",label_app="example1"} 1
				kube_replicaset_created{namespace="ns1",replicaset="rs1"} 1.5e+09
				kube_replicaset_metadata_generation{namespace="ns1",replicaset="rs1"} 21
				kube_replicaset_status_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_status_observed_generation{namespace="ns1",replicaset="rs1"} 1
				kube_replicaset_status_fully_labeled_replicas{namespace="ns1",replicaset="rs1"} 10
				kube_replicaset_status_ready_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_spec_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_owner{namespace="ns1",owner_is_controller="true",owner_kind="Deployment",owner_name="dp-name",replicaset="rs1"} 1
`,
		},
		{
			Obj: &v1beta1.ReplicaSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:       "rs2",
					Namespace:  "ns2",
					Generation: 14,
					Labels: map[string]string{
						"app": "example2",
						"env": "ex",
					},
				},
				Status: v1beta1.ReplicaSetStatus{
					Replicas:             0,
					FullyLabeledReplicas: 5,
					ReadyReplicas:        0,
					ObservedGeneration:   5,
				},
				Spec: v1beta1.ReplicaSetSpec{
					Replicas: &rs2Replicas,
				},
			},
			Want: `
				kube_replicaset_labels{replicaset="rs2",namespace="ns2",label_app="example2",label_env="ex"} 1
				kube_replicaset_metadata_generation{namespace="ns2",replicaset="rs2"} 14
				kube_replicaset_status_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_status_observed_generation{namespace="ns2",replicaset="rs2"} 5
				kube_replicaset_status_fully_labeled_replicas{namespace="ns2",replicaset="rs2"} 5
				kube_replicaset_status_ready_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_spec_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_owner{namespace="ns2",owner_is_controller="<none>",owner_kind="<none>",owner_name="<none>",replicaset="rs2"} 1
			`,
		},
	}
	for i, c := range cases {
		c.Func = composeMetricGenFuncs(replicaSetMetricFamilies)
		if err := c.run(); err != nil {
			t.Errorf("unexpected collecting result in %vth run:\n%s", i, err)
		}

	}
}
<commit_msg>Added ns1 to first kube_replicaset_labels test<commit_after>/*
Copyright 2016 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 collectors

import (
	"testing"
	"time"

	"k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
	rs1Replicas int32 = 5
	rs2Replicas int32 = 0
)

func TestReplicaSetCollector(t *testing.T) {
	// Fixed metadata on type and help text. We prepend this to every expected
	// output so we only have to modify a single place when doing adjustments.
	var test = true

	const metadata = `
		# HELP kube_replicaset_created Unix creation timestamp
		# TYPE kube_replicaset_created gauge
	  # HELP kube_replicaset_metadata_generation Sequence number representing a specific generation of the desired state.
		# TYPE kube_replicaset_metadata_generation gauge
		# HELP kube_replicaset_status_replicas The number of replicas per ReplicaSet.
		# TYPE kube_replicaset_status_replicas gauge
		# HELP kube_replicaset_status_fully_labeled_replicas The number of fully labeled replicas per ReplicaSet.
		# TYPE kube_replicaset_status_fully_labeled_replicas gauge
		# HELP kube_replicaset_status_ready_replicas The number of ready replicas per ReplicaSet.
		# TYPE kube_replicaset_status_ready_replicas gauge
		# HELP kube_replicaset_status_observed_generation The generation observed by the ReplicaSet controller.
		# TYPE kube_replicaset_status_observed_generation gauge
		# HELP kube_replicaset_spec_replicas Number of desired pods for a ReplicaSet.
		# TYPE kube_replicaset_spec_replicas gauge
		# HELP kube_replicaset_owner Information about the ReplicaSet's owner.
		# TYPE kube_replicaset_owner gauge
		# HELP kube_replicaset_labels Kubernetes labels converted to Prometheus labels.
		# TYPE kube_replicaset_labels gauge
	`
	cases := []generateMetricsTestCase{
		{
			Obj: &v1beta1.ReplicaSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:              "rs1",
					CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)},
					Namespace:         "ns1",
					Generation:        21,
					OwnerReferences: []metav1.OwnerReference{
						{
							Kind:       "Deployment",
							Name:       "dp-name",
							Controller: &test,
						},
					},
					Labels: map[string]string{
						"app": "example1",
					},
				},
				Status: v1beta1.ReplicaSetStatus{
					Replicas:             5,
					FullyLabeledReplicas: 10,
					ReadyReplicas:        5,
					ObservedGeneration:   1,
				},
				Spec: v1beta1.ReplicaSetSpec{
					Replicas: &rs1Replicas,
				},
			},
			Want: `
				kube_replicaset_labels{replicaset="rs1",namespace="ns1",label_app="example1"} 1
				kube_replicaset_created{namespace="ns1",replicaset="rs1"} 1.5e+09
				kube_replicaset_metadata_generation{namespace="ns1",replicaset="rs1"} 21
				kube_replicaset_status_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_status_observed_generation{namespace="ns1",replicaset="rs1"} 1
				kube_replicaset_status_fully_labeled_replicas{namespace="ns1",replicaset="rs1"} 10
				kube_replicaset_status_ready_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_spec_replicas{namespace="ns1",replicaset="rs1"} 5
				kube_replicaset_owner{namespace="ns1",owner_is_controller="true",owner_kind="Deployment",owner_name="dp-name",replicaset="rs1"} 1
`,
		},
		{
			Obj: &v1beta1.ReplicaSet{
				ObjectMeta: metav1.ObjectMeta{
					Name:       "rs2",
					Namespace:  "ns2",
					Generation: 14,
					Labels: map[string]string{
						"app": "example2",
						"env": "ex",
					},
				},
				Status: v1beta1.ReplicaSetStatus{
					Replicas:             0,
					FullyLabeledReplicas: 5,
					ReadyReplicas:        0,
					ObservedGeneration:   5,
				},
				Spec: v1beta1.ReplicaSetSpec{
					Replicas: &rs2Replicas,
				},
			},
			Want: `
				kube_replicaset_labels{replicaset="rs2",namespace="ns2",label_app="example2",label_env="ex"} 1
				kube_replicaset_metadata_generation{namespace="ns2",replicaset="rs2"} 14
				kube_replicaset_status_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_status_observed_generation{namespace="ns2",replicaset="rs2"} 5
				kube_replicaset_status_fully_labeled_replicas{namespace="ns2",replicaset="rs2"} 5
				kube_replicaset_status_ready_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_spec_replicas{namespace="ns2",replicaset="rs2"} 0
				kube_replicaset_owner{namespace="ns2",owner_is_controller="<none>",owner_kind="<none>",owner_name="<none>",replicaset="rs2"} 1
			`,
		},
	}
	for i, c := range cases {
		c.Func = composeMetricGenFuncs(replicaSetMetricFamilies)
		if err := c.run(); err != nil {
			t.Errorf("unexpected collecting result in %vth run:\n%s", i, err)
		}

	}
}
<|endoftext|>


<commit_before>package node

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/kube-node/kube-machine/pkg/controller"
	"github.com/kube-node/kube-machine/pkg/nodeclass"
	"github.com/kube-node/nodeset/pkg/nodeset/v1alpha1"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

type Controller struct {
	nodeInformer         cache.Controller
	nodeIndexer          cache.Indexer
	nodeQueue            workqueue.RateLimitingInterface
	nodeClassStore       cache.Store
	nodeClassInformer    cache.Controller
	client               *kubernetes.Clientset
	nodeCreateLock       *sync.Mutex
	maxMigrationWaitTime time.Duration
	metrics              *ControllerMetrics
}

const (
	phaseAnnotationKey      = "node.k8s.io/state"
	driverDataAnnotationKey = "node.k8s.io/driver-data"
	publicIPAnnotationKey   = "node.k8s.io/public-ip"
	hostnameAnnotationKey   = "node.k8s.io/hostname"

	deleteFinalizerName     = "node.k8s.io/delete"

	controllerName     = "kube-machine"

	phasePending      = "pending"
	phaseProvisioning = "provisioning"
	phaseLaunching    = "launching"
	phaseRunning      = "running"
	phaseDeleting     = "deleting"

	conditionUpdatePeriod = 5 * time.Second
	migrationWorkerPeriod = 5 * time.Second
)

var nodeClassNotFoundErr = errors.New("node class not found")
var nodeNotFoundErr = errors.New("node not found")
var noNodeClassDefinedErr = errors.New("no node class defined")

func New(
	client *kubernetes.Clientset,
	queue workqueue.RateLimitingInterface,
	nodeIndexer cache.Indexer,
	nodeInformer cache.Controller,
	nodeClassStore cache.Store,
	nodeClassController cache.Controller,
	maxMigrationWaitTime time.Duration,
	metrics *ControllerMetrics,
) controller.Interface {
	return &Controller{
		nodeInformer:         nodeInformer,
		nodeIndexer:          nodeIndexer,
		nodeQueue:            queue,
		nodeClassInformer:    nodeClassController,
		nodeClassStore:       nodeClassStore,
		client:               client,
		nodeCreateLock:       &sync.Mutex{},
		maxMigrationWaitTime: maxMigrationWaitTime,
		metrics:              metrics,
	}
}

func (c *Controller) processNextItem() bool {
	// Wait until there is a new item in the working nodeQueue
	key, quit := c.nodeQueue.Get()
	if quit {
		return false
	}

	defer c.nodeQueue.Done(key)

	err := c.syncNode(key.(string))
	if err != nil {
		c.metrics.SyncErrors.Inc()
	}

	c.handleErr(err, key)
	return true
}

func (c *Controller) getNode(key string) (*corev1.Node, error) {
	node, err := c.client.CoreV1().Nodes().Get(key, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return node, nil
}

func (c *Controller) syncNode(key string) error {
	node, err := c.getNode(key)
	if err != nil {
		glog.V(0).Infof("Failed to fetch node %s: %v", key, err)
		return nil
	}

	isControllerNode, err := c.isControllerNode(node)
	if err != nil {
		return fmt.Errorf("failed to identify if node %s belongs to this controller: %v", node.Name, err)
	}
	if !isControllerNode {
		glog.V(8).Infof("Skipping node %s as the specified node-controller != %s", node.Name, controllerName)
		return nil
	}

	originalData, err := json.Marshal(node)
	if err != nil {
		glog.V(0).Infof("Failed marshal node %s: %v", key, err)
		return nil
	}

	glog.V(6).Infof("Processing Node %s\n", node.GetName())

	// Get phase of node. In case we have not touched it set phase to `pending`
	phase := node.Annotations[phaseAnnotationKey]
	if phase == "" {
		phase = phasePending
	}

	if node.DeletionTimestamp != nil {
		phase = phaseDeleting
	}
	node.Annotations[phaseAnnotationKey] = phase

	start := time.Now()

	switch phase {
	case phasePending:
		node, err = c.syncPendingNode(node)
	case phaseProvisioning:
		node, err = c.syncProvisioningNode(node)
	case phaseLaunching:
		node, err = c.syncLaunchingNode(node)
	case phaseDeleting:
		node, err = c.syncDeletingNode(node)
	}

	if phase != phaseRunning {
		c.metrics.SyncSeconds.WithLabelValues(phase).Add(time.Since(start).Seconds())
	}

	if err != nil {
		return err
	}

	if node != nil {
		return c.updateNode(originalData, node)
	}

	c.nodeQueue.AddAfter(key, 30*time.Second)
	return nil
}

func (c *Controller) getNodeClassConfig(nc *v1alpha1.NodeClass) (*nodeclass.NodeClassConfig, error) {
	var config nodeclass.NodeClassConfig
	err := json.Unmarshal(nc.Config.Raw, &config)
	if err != nil {
		return nil, fmt.Errorf("could not unmarshal config from nodeclass: %v", err)
	}
	return &config, nil
}

func (c *Controller) getNodeClassFromAnnotationContent(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	content, err := base64.StdEncoding.DecodeString(node.Annotations[v1alpha1.NodeClassContentAnnotationKey])
	if err != nil {
		return nil, nil, fmt.Errorf("failed to load nodeclass content from annotation %s: %v", v1alpha1.NodeClassContentAnnotationKey, err)
	}
	class := &v1alpha1.NodeClass{}
	err = json.Unmarshal(content, class)
	if err != nil {
		return nil, nil, fmt.Errorf("could not unmarshal nodeclass from annotation %s content: %v", v1alpha1.NodeClassContentAnnotationKey, err)
	}
	config, err := c.getNodeClassConfig(class)
	return class, config, err
}

func (c *Controller) getNodeClassFromAnnotation(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	ncobj, exists, err := c.nodeClassStore.GetByKey(node.Annotations[v1alpha1.NodeClassNameAnnotationKey])
	if err != nil {
		return nil, nil, fmt.Errorf("could not fetch nodeclass from store: %v", err)
	}
	if !exists {
		return nil, nil, nodeClassNotFoundErr
	}

	class := ncobj.(*v1alpha1.NodeClass)
	config, err := c.getNodeClassConfig(class)
	return class, config, err
}

func (c *Controller) getNodeClass(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	if node.Annotations[v1alpha1.NodeClassContentAnnotationKey] == "" && node.Annotations[v1alpha1.NodeClassNameAnnotationKey] == "" {
		return nil, nil, noNodeClassDefinedErr
	}

	//First try to load it via annotation
	if node.Annotations[v1alpha1.NodeClassContentAnnotationKey] != "" {
		return c.getNodeClassFromAnnotationContent(node)
	}
	return c.getNodeClassFromAnnotation(node)
}

func (c *Controller) updateNode(originalData []byte, node *corev1.Node) error {
	modifiedData, err := json.Marshal(node)
	if err != nil {
		return err
	}

	b, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, corev1.Node{})
	if err != nil {
		return err
	}
	//Avoid empty patch calls
	if string(b) == "{}" {
		return nil
	}

	_, err = c.client.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, b)
	return err
}

// handleErr checks if an error happened and makes sure we will retry later.
func (c *Controller) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		c.nodeQueue.Forget(key)
		return
	}

	// This controller retries 5 times if something goes wrong. After that, it stops trying.
	if c.nodeQueue.NumRequeues(key) < 5 {
		glog.V(0).Infof("Error syncing node %v: %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// nodeQueue and the re-enqueue history, the key will be processed later again.
		c.nodeQueue.AddRateLimited(key)
		return
	}

	c.nodeQueue.Forget(key)
	// Report to an external entity that, even after several retries, we could not successfully process this key
	runtime.HandleError(err)
	glog.V(0).Infof("Dropping node %q out of the queue: %v", key, err)
}

func (c *Controller) Run(workerCount int, stopCh chan struct{}) {
	defer runtime.HandleCrash()

	// Let the workers stop when we are done
	defer c.nodeQueue.ShutDown()
	glog.V(0).Info("Starting Node controller")

	go c.nodeInformer.Run(stopCh)
	go c.nodeClassInformer.Run(stopCh)

	// Wait for all involved caches to be synced, before processing items from the nodeQueue is started
	if !cache.WaitForCacheSync(stopCh, c.nodeInformer.HasSynced, c.nodeClassInformer.HasSynced) {
		runtime.HandleError(errors.New("timed out waiting for caches to sync"))
		return
	}

	go wait.Forever(func() {
		c.metrics.Nodes.Set(float64(len(c.nodeIndexer.List())))
	}, time.Second)

	for i := 0; i < workerCount; i++ {
		go wait.Until(c.runWorker, time.Second, stopCh)
	}
	go wait.Forever(c.readyConditionWorker, conditionUpdatePeriod)
	go wait.Forever(c.migrationWorker, migrationWorkerPeriod)

	<-stopCh
	glog.V(0).Info("Stopping Node controller")
	glog.V(0).Info("Waiting until all pending migrations are done...")
	c.waitUntilMigrationDone()
	glog.V(0).Info("Done")
}

func (c *Controller) runWorker() {
	for c.processNextItem() {
	}
}

func (c *Controller) IsReady() bool {
	return c.nodeInformer.HasSynced() && c.nodeClassInformer.HasSynced()
}
<commit_msg>Ignore deletion error<commit_after>package node

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/kube-node/kube-machine/pkg/controller"
	"github.com/kube-node/kube-machine/pkg/nodeclass"
	"github.com/kube-node/nodeset/pkg/nodeset/v1alpha1"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
)

type Controller struct {
	nodeInformer         cache.Controller
	nodeIndexer          cache.Indexer
	nodeQueue            workqueue.RateLimitingInterface
	nodeClassStore       cache.Store
	nodeClassInformer    cache.Controller
	client               *kubernetes.Clientset
	nodeCreateLock       *sync.Mutex
	maxMigrationWaitTime time.Duration
	metrics              *ControllerMetrics
}

const (
	phaseAnnotationKey      = "node.k8s.io/state"
	driverDataAnnotationKey = "node.k8s.io/driver-data"
	publicIPAnnotationKey   = "node.k8s.io/public-ip"
	hostnameAnnotationKey   = "node.k8s.io/hostname"

	deleteFinalizerName     = "node.k8s.io/delete"

	controllerName     = "kube-machine"

	phasePending      = "pending"
	phaseProvisioning = "provisioning"
	phaseLaunching    = "launching"
	phaseRunning      = "running"
	phaseDeleting     = "deleting"

	conditionUpdatePeriod = 5 * time.Second
	migrationWorkerPeriod = 5 * time.Second
)

var nodeClassNotFoundErr = errors.New("node class not found")
var nodeNotFoundErr = errors.New("node not found")
var noNodeClassDefinedErr = errors.New("no node class defined")

func New(
	client *kubernetes.Clientset,
	queue workqueue.RateLimitingInterface,
	nodeIndexer cache.Indexer,
	nodeInformer cache.Controller,
	nodeClassStore cache.Store,
	nodeClassController cache.Controller,
	maxMigrationWaitTime time.Duration,
	metrics *ControllerMetrics,
) controller.Interface {
	return &Controller{
		nodeInformer:         nodeInformer,
		nodeIndexer:          nodeIndexer,
		nodeQueue:            queue,
		nodeClassInformer:    nodeClassController,
		nodeClassStore:       nodeClassStore,
		client:               client,
		nodeCreateLock:       &sync.Mutex{},
		maxMigrationWaitTime: maxMigrationWaitTime,
		metrics:              metrics,
	}
}

func (c *Controller) processNextItem() bool {
	// Wait until there is a new item in the working nodeQueue
	key, quit := c.nodeQueue.Get()
	if quit {
		return false
	}

	defer c.nodeQueue.Done(key)

	err := c.syncNode(key.(string))
	if err != nil {
		c.metrics.SyncErrors.Inc()
	}

	c.handleErr(err, key)
	return true
}

func (c *Controller) getNode(key string) (*corev1.Node, error) {
	node, err := c.client.CoreV1().Nodes().Get(key, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return node, nil
}

func (c *Controller) syncNode(key string) error {
	node, err := c.getNode(key)
	if err != nil {
		if kerrors.IsNotFound(err) {
			glog.V(6).Infof("Node %s got deleted", key)
			return nil
		}
		glog.V(0).Infof("Failed to fetch node %s: %v", key, err)
		return nil
	}

	isControllerNode, err := c.isControllerNode(node)
	if err != nil {
		return fmt.Errorf("failed to identify if node %s belongs to this controller: %v", node.Name, err)
	}
	if !isControllerNode {
		glog.V(8).Infof("Skipping node %s as the specified node-controller != %s", node.Name, controllerName)
		return nil
	}

	originalData, err := json.Marshal(node)
	if err != nil {
		glog.V(0).Infof("Failed marshal node %s: %v", key, err)
		return nil
	}

	glog.V(6).Infof("Processing Node %s\n", node.GetName())

	// Get phase of node. In case we have not touched it set phase to `pending`
	phase := node.Annotations[phaseAnnotationKey]
	if phase == "" {
		phase = phasePending
	}

	if node.DeletionTimestamp != nil {
		phase = phaseDeleting
	}
	node.Annotations[phaseAnnotationKey] = phase

	start := time.Now()

	switch phase {
	case phasePending:
		node, err = c.syncPendingNode(node)
	case phaseProvisioning:
		node, err = c.syncProvisioningNode(node)
	case phaseLaunching:
		node, err = c.syncLaunchingNode(node)
	case phaseDeleting:
		node, err = c.syncDeletingNode(node)
	}

	if phase != phaseRunning {
		c.metrics.SyncSeconds.WithLabelValues(phase).Add(time.Since(start).Seconds())
	}

	if err != nil {
		return err
	}

	if node != nil {
		return c.updateNode(originalData, node)
	}

	c.nodeQueue.AddAfter(key, 30*time.Second)
	return nil
}

func (c *Controller) getNodeClassConfig(nc *v1alpha1.NodeClass) (*nodeclass.NodeClassConfig, error) {
	var config nodeclass.NodeClassConfig
	err := json.Unmarshal(nc.Config.Raw, &config)
	if err != nil {
		return nil, fmt.Errorf("could not unmarshal config from nodeclass: %v", err)
	}
	return &config, nil
}

func (c *Controller) getNodeClassFromAnnotationContent(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	content, err := base64.StdEncoding.DecodeString(node.Annotations[v1alpha1.NodeClassContentAnnotationKey])
	if err != nil {
		return nil, nil, fmt.Errorf("failed to load nodeclass content from annotation %s: %v", v1alpha1.NodeClassContentAnnotationKey, err)
	}
	class := &v1alpha1.NodeClass{}
	err = json.Unmarshal(content, class)
	if err != nil {
		return nil, nil, fmt.Errorf("could not unmarshal nodeclass from annotation %s content: %v", v1alpha1.NodeClassContentAnnotationKey, err)
	}
	config, err := c.getNodeClassConfig(class)
	return class, config, err
}

func (c *Controller) getNodeClassFromAnnotation(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	ncobj, exists, err := c.nodeClassStore.GetByKey(node.Annotations[v1alpha1.NodeClassNameAnnotationKey])
	if err != nil {
		return nil, nil, fmt.Errorf("could not fetch nodeclass from store: %v", err)
	}
	if !exists {
		return nil, nil, nodeClassNotFoundErr
	}

	class := ncobj.(*v1alpha1.NodeClass)
	config, err := c.getNodeClassConfig(class)
	return class, config, err
}

func (c *Controller) getNodeClass(node *corev1.Node) (*v1alpha1.NodeClass, *nodeclass.NodeClassConfig, error) {
	if node.Annotations[v1alpha1.NodeClassContentAnnotationKey] == "" && node.Annotations[v1alpha1.NodeClassNameAnnotationKey] == "" {
		return nil, nil, noNodeClassDefinedErr
	}

	//First try to load it via annotation
	if node.Annotations[v1alpha1.NodeClassContentAnnotationKey] != "" {
		return c.getNodeClassFromAnnotationContent(node)
	}
	return c.getNodeClassFromAnnotation(node)
}

func (c *Controller) updateNode(originalData []byte, node *corev1.Node) error {
	modifiedData, err := json.Marshal(node)
	if err != nil {
		return err
	}

	b, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, corev1.Node{})
	if err != nil {
		return err
	}
	//Avoid empty patch calls
	if string(b) == "{}" {
		return nil
	}

	_, err = c.client.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, b)
	return err
}

// handleErr checks if an error happened and makes sure we will retry later.
func (c *Controller) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		c.nodeQueue.Forget(key)
		return
	}

	// This controller retries 5 times if something goes wrong. After that, it stops trying.
	if c.nodeQueue.NumRequeues(key) < 5 {
		glog.V(0).Infof("Error syncing node %v: %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// nodeQueue and the re-enqueue history, the key will be processed later again.
		c.nodeQueue.AddRateLimited(key)
		return
	}

	c.nodeQueue.Forget(key)
	// Report to an external entity that, even after several retries, we could not successfully process this key
	runtime.HandleError(err)
	glog.V(0).Infof("Dropping node %q out of the queue: %v", key, err)
}

func (c *Controller) Run(workerCount int, stopCh chan struct{}) {
	defer runtime.HandleCrash()

	// Let the workers stop when we are done
	defer c.nodeQueue.ShutDown()
	glog.V(0).Info("Starting Node controller")

	go c.nodeInformer.Run(stopCh)
	go c.nodeClassInformer.Run(stopCh)

	// Wait for all involved caches to be synced, before processing items from the nodeQueue is started
	if !cache.WaitForCacheSync(stopCh, c.nodeInformer.HasSynced, c.nodeClassInformer.HasSynced) {
		runtime.HandleError(errors.New("timed out waiting for caches to sync"))
		return
	}

	go wait.Forever(func() {
		c.metrics.Nodes.Set(float64(len(c.nodeIndexer.List())))
	}, time.Second)

	for i := 0; i < workerCount; i++ {
		go wait.Until(c.runWorker, time.Second, stopCh)
	}
	go wait.Forever(c.readyConditionWorker, conditionUpdatePeriod)
	go wait.Forever(c.migrationWorker, migrationWorkerPeriod)

	<-stopCh
	glog.V(0).Info("Stopping Node controller")
	glog.V(0).Info("Waiting until all pending migrations are done...")
	c.waitUntilMigrationDone()
	glog.V(0).Info("Done")
}

func (c *Controller) runWorker() {
	for c.processNextItem() {
	}
}

func (c *Controller) IsReady() bool {
	return c.nodeInformer.HasSynced() && c.nodeClassInformer.HasSynced()
}
<|endoftext|>