<commit_before>package koding

import (
	"fmt"
	"koding/db/mongodb"
	"koding/kites/kloud/klient"
	"strconv"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"github.com/koding/kite"
	aws "github.com/koding/kloud/api/amazon"
	"github.com/koding/kloud/machinestate"
	"github.com/koding/kloud/protocol"
	"github.com/koding/kloud/provider/amazon"
	"github.com/koding/logging"
	"github.com/mitchellh/goamz/ec2"
)

// Checker checks various aspects of a machine. It used to limit certain
// aspects of a machine, such a the total machine or total storage.
type Checker interface {
	// Total checks whether the user has reached the current plan's limit of
	// having a total number numbers of machines. It returns an error if the
	// limit is reached or an unexplained error happaned.
	Total() error

	// AlwaysOn checks whether the given machine has reached the current plans
	// always on limit
	AlwaysOn() error

	// Timeout checks whether the user has reached the current plan's
	// inactivity timeout.
	Timeout() error

	// Storage checks whether the user has reached the current plan's limit
	// total storage with the supplied wantStorage information. It returns an
	// error if the limit is reached or an unexplained error happaned.
	Storage(wantStorage int) error

	// AllowedInstances checks whether the given machine has the permisison to
	// create the given instance type
	AllowedInstances(wantInstance InstanceType) error
}

type PlanChecker struct {
	api      *amazon.AmazonClient
	db       *mongodb.MongoDB
	machine  *protocol.Machine
	provider *Provider
	kite     *kite.Kite
	username string
	log      logging.Logger
}

// PlanChecker creates and returns a new Checker interface that is responsible
// of checking various pieces of informations based on a Plan
func (p *Provider) PlanChecker(opts *protocol.Machine) (Checker, error) {
	a, err := p.NewClient(opts)
	if err != nil {
		return nil, err
	}

	checker := &PlanChecker{
		api:      a,
		provider: p,
		db:       p.Session,
		kite:     p.Kite,
		username: opts.Builder["username"].(string),
		log:      p.Log,
		machine:  opts,
	}

	return checker, nil
}

// Plan returns user's current plan
func (p *PlanChecker) Plan() (Plan, error) {
	return Free, nil
}

func (p *PlanChecker) AllowedInstances(wantInstance InstanceType) error {
	plan, err := p.Plan()
	if err != nil {
		return err
	}

	allowedInstances := plan.Limits().AllowedInstances

	p.log.Info("[%s] checking instance type. want: %s (plan: %s)",
		p.machine.MachineId, wantInstance, plan)

	if _, ok := allowedInstances[wantInstance]; ok {
		return nil
	}

	return fmt.Errorf("not allowed to create instance type: %s", wantInstance)
}

func (p *PlanChecker) AlwaysOn() error {
	plan, err := p.Plan()
	if err != nil {
		return err
	}

	machineData, ok := p.machine.CurrentData.(*Machine)
	if !ok {
		return fmt.Errorf("current data is malformed: %v", p.machine.CurrentData)
	}

	alwaysOnLimit := plan.Limits().AlwaysOn

	alwaysOnEnabled := false
	if has, ok := p.machine.Builder["alwaysOn"]; ok {
		if alwaysOnEnabled, ok = has.(bool); !ok {
			return fmt.Errorf("alwaysOn data is malformed %v", has)
		}
	} else {
		// it doesn't exists, so give access to continue
		return nil
	}

	// disabled give access
	if !alwaysOnEnabled {
		return nil
	}

	user := machineData.Users[0]

	// get all machines that belongs to this user
	alwaysOnMachines := 0
	err = p.db.Run("jMachines", func(c *mgo.Collection) error {
		alwaysOnMachines, err = c.Find(bson.M{
			"users.id": user.Id,
		}).Count()

		return err
	})

	// if it's something else just return an error, needs to be fixed
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	p.log.Info("[%s] checking alwaysOn limit. current alwaysOn count: %d (plan limit: %d, plan: %s)",
		p.machine.MachineId, alwaysOnMachines, alwaysOnLimit, plan)
	// the user has still not reached the limit
	if alwaysOnMachines < alwaysOnLimit {
		p.log.Info("[%s] allowing user '%s'. current alwaysOn count: %d (plan limit: %d, plan: %s)",
			p.machine.MachineId, p.username, alwaysOnMachines, alwaysOnLimit, plan)
		return nil
	}
<commit_msg>kloud/checker: fix alwaysOn checking. Include all machines<commit_after>package koding

import (
	"fmt"
	"koding/db/mongodb"
	"koding/kites/kloud/klient"
	"strconv"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"github.com/koding/kite"
	aws "github.com/koding/kloud/api/amazon"
	"github.com/koding/kloud/machinestate"
	"github.com/koding/kloud/protocol"
	"github.com/koding/kloud/provider/amazon"
	"github.com/koding/logging"
	"github.com/mitchellh/goamz/ec2"
)

// Checker checks various aspects of a machine. It used to limit certain
// aspects of a machine, such a the total machine or total storage.
type Checker interface {
	// Total checks whether the user has reached the current plan's limit of
	// having a total number numbers of machines. It returns an error if the
	// limit is reached or an unexplained error happaned.
	Total() error

	// AlwaysOn checks whether the given machine has reached the current plans
	// always on limit
	AlwaysOn() error

	// Timeout checks whether the user has reached the current plan's
	// inactivity timeout.
	Timeout() error

	// Storage checks whether the user has reached the current plan's limit
	// total storage with the supplied wantStorage information. It returns an
	// error if the limit is reached or an unexplained error happaned.
	Storage(wantStorage int) error

	// AllowedInstances checks whether the given machine has the permisison to
	// create the given instance type
	AllowedInstances(wantInstance InstanceType) error
}

type PlanChecker struct {
	api      *amazon.AmazonClient
	db       *mongodb.MongoDB
	machine  *protocol.Machine
	provider *Provider
	kite     *kite.Kite
	username string
	log      logging.Logger
}

// PlanChecker creates and returns a new Checker interface that is responsible
// of checking various pieces of informations based on a Plan
func (p *Provider) PlanChecker(opts *protocol.Machine) (Checker, error) {
	a, err := p.NewClient(opts)
	if err != nil {
		return nil, err
	}

	checker := &PlanChecker{
		api:      a,
		provider: p,
		db:       p.Session,
		kite:     p.Kite,
		username: opts.Builder["username"].(string),
		log:      p.Log,
		machine:  opts,
	}

	return checker, nil
}

// Plan returns user's current plan
func (p *PlanChecker) Plan() (Plan, error) {
	return Free, nil
}

func (p *PlanChecker) AllowedInstances(wantInstance InstanceType) error {
	plan, err := p.Plan()
	if err != nil {
		return err
	}

	allowedInstances := plan.Limits().AllowedInstances

	p.log.Info("[%s] checking instance type. want: %s (plan: %s)",
		p.machine.MachineId, wantInstance, plan)

	if _, ok := allowedInstances[wantInstance]; ok {
		return nil
	}

	return fmt.Errorf("not allowed to create instance type: %s", wantInstance)
}

func (p *PlanChecker) AlwaysOn() error {
	plan, err := p.Plan()
	if err != nil {
		return err
	}

	machineData, ok := p.machine.CurrentData.(*Machine)
	if !ok {
		return fmt.Errorf("current data is malformed: %v", p.machine.CurrentData)
	}

	alwaysOnLimit := plan.Limits().AlwaysOn

	user := machineData.Users[0]

	// get all alwaysOn machines that belongs to this user
	alwaysOnMachines := 0
	err = p.db.Run("jMachines", func(c *mgo.Collection) error {
		alwaysOnMachines, err = c.Find(bson.M{
			"users.id":      user.Id,
			"meta.alwaysOn": true,
		}).Count()

		return err
	})

	// if it's something else just return an error, needs to be fixed
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	p.log.Info("[%s] checking alwaysOn limit. current alwaysOn count: %d (plan limit: %d, plan: %s)",
		p.machine.MachineId, alwaysOnMachines, alwaysOnLimit, plan)
	// the user has still not reached the limit
	if alwaysOnMachines <= alwaysOnLimit {
		p.log.Info("[%s] allowing user '%s'. current alwaysOn count: %d (plan limit: %d, plan: %s)",
			p.machine.MachineId, p.username, alwaysOnMachines, alwaysOnLimit, plan)
		return nil
	}

{'fraction_non_alphanumeric': 0.1125062782521346, 'fraction_numerical': 0.0012556504269211452, 'mean_word_length': 3.6660808435852372, 'pattern_counts': {'":': 3, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5990461', 'n_tokens_mistral': 2716, 'n_tokens_neox': 2600, 'n_words': 866}