api/node.go
func rebalanceNodesHandler(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	var params provision.RebalanceNodesOptions
	err = ParseInput(r, &params)
	if err != nil {
		return &tsuruErrors.HTTP{
			Code:    http.StatusBadRequest,
			Message: err.Error(),
		}
	}
	params.Force = true
	var permContexts []permTypes.PermissionContext
	var ok bool
	evtTarget := event.Target{Type: event.TargetTypeGlobal}
	params.Pool, ok = params.MetadataFilter[provision.PoolMetadataName]
	if ok {
		delete(params.MetadataFilter, provision.PoolMetadataName)
		permContexts = append(permContexts, permission.Context(permTypes.CtxPool, params.Pool))
		evtTarget = event.Target{Type: event.TargetTypePool, Value: params.Pool}
	}
	if !permission.Check(t, permission.PermNodeUpdateRebalance, permContexts...) {
		return permission.ErrUnauthorized
	}
	evt, err := event.New(&event.Opts{
		Target:        evtTarget,
		Kind:          permission.PermNodeUpdateRebalance,
		Owner:         t,
		CustomData:    event.FormToCustomData(InputFields(r)),
		DisableLock:   true,
		Allowed:       event.Allowed(permission.PermPoolReadEvents, permContexts...),
		Cancelable:    true,
		AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, permContexts...),
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	w.Header().Set("Content-Type", "application/x-json-stream")
	keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "")
	defer keepAliveWriter.Stop()
	writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)}
	evt.SetLogWriter(writer)
	params.Event = evt
	var provs []provision.Provisioner
	if params.Pool != "" {
		var p *pool.Pool
		var prov provision.Provisioner
		p, err = pool.GetPoolByName(params.Pool)
		if err != nil {
			return err
		}
		prov, err = p.GetProvisioner()
		if err != nil {
			return err
		}
		if _, ok := prov.(provision.NodeRebalanceProvisioner); !ok {
			return provision.ProvisionerNotSupported{Prov: prov, Action: "node rebalance operations"}
		}
		provs = append(provs, prov)
	} else {
		provs, err = provision.Registry()
		if err != nil {
			return err
		}
	}
	for _, prov := range provs {
		rebalanceProv, ok := prov.(provision.NodeRebalanceProvisioner)
		if !ok {
			continue
		}
		_, err = rebalanceProv.RebalanceNodes(params)
		if err != nil {
			return errors.Wrap(err, "Error trying to rebalance units in nodes")
		}
	}
	fmt.Fprintf(writer, "Units successfully rebalanced!\n")
	return nil
}
func rebalanceNodesHandler(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	var params provision.RebalanceNodesOptions
	err = ParseInput(r, &params)
	if err != nil {
		return &tsuruErrors.HTTP{
			Code:    http.StatusBadRequest,
			Message: err.Error(),
		}
	}
	params.Force = true
	var permContexts []permTypes.PermissionContext
	var ok bool
	evtTarget := event.Target{Type: event.TargetTypeGlobal}
	params.Pool, ok = params.MetadataFilter[provision.PoolMetadataName]
	if ok {
		delete(params.MetadataFilter, provision.PoolMetadataName)
		permContexts = append(permContexts, permission.Context(permTypes.CtxPool, params.Pool))
		evtTarget = event.Target{Type: event.TargetTypePool, Value: params.Pool}
	}
	if !permission.Check(t, permission.PermNodeUpdateRebalance, permContexts...) {
		return permission.ErrUnauthorized
	}
	evt, err := event.New(&event.Opts{
		Target:        evtTarget,
		Kind:          permission.PermNodeUpdateRebalance,
		Owner:         t,
		CustomData:    event.FormToCustomData(InputFields(r)),
		DisableLock:   true,
		Allowed:       event.Allowed(permission.PermPoolReadEvents, permContexts...),
		Cancelable:    true,
		AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, permContexts...),
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	w.Header().Set("Content-Type", "application/x-json-stream")
	keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 15*time.Second, "")
	defer keepAliveWriter.Stop()
	writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)}
	evt.SetLogWriter(writer)
	params.Event = evt
	var provs []provision.Provisioner
	if params.Pool != "" {
		var p *pool.Pool
		var prov provision.Provisioner
		p, err = pool.GetPoolByName(params.Pool)
		if err != nil {
			return err
		}
		prov, err = p.GetProvisioner()
		if err != nil {
			return err
		}
		if _, ok := prov.(provision.NodeRebalanceProvisioner); !ok {
			return provision.ProvisionerNotSupported{Prov: prov, Action: "node rebalance operations"}
		}
		provs = append(provs, prov)
	} else {
		provs, err = provision.Registry()
		if err != nil {
			return err
		}
	}
	for _, prov := range provs {
		rebalanceProv, ok := prov.(provision.NodeRebalanceProvisioner)
		if !ok {
			continue
		}
		_, err = rebalanceProv.RebalanceNodes(params)
		if err != nil {
			return errors.Wrap(err, "Error trying to rebalance units in nodes")
		}
	}
	fmt.Fprintf(writer, "Units successfully rebalanced!\n")
	return nil
}
// title: rebalance units in nodes
// path: /node/rebalance
// method: POST
// consume: application/x-www-form-urlencoded
// produce: application/x-json-stream
// responses:
//   200: Ok
//   400: Invalid data
//   401: Unauthorized