<commit_before>package cloudbrain

import (
	"crypto/subtle"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/garyburd/redigo/redis"
	"github.com/gocraft/work"
	"github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
	"github.com/travis-ci/cloud-brain/cbcontext"
	"github.com/travis-ci/cloud-brain/cloud"
	"github.com/travis-ci/cloud-brain/database"
	"golang.org/x/crypto/scrypt"
	"golang.org/x/net/context"
	"gopkg.in/urfave/cli.v2"
)

var (
	//VersionString gets set during `make`
	VersionString = "?"
	//RevisionString gets set during `make`
	RevisionString = "?"
	//RevisionURLString gets set during `make`
	RevisionURLString = "?"
	//GeneratedString gets set during `make`
	GeneratedString = "?"
	//CopyrightString gets set during `make`
	CopyrightString = "?"
)

func init() {
	cli.VersionPrinter = customVersionPrinter
	_ = os.Setenv("VERSION", VersionString)
	_ = os.Setenv("REVISION", RevisionString)
	_ = os.Setenv("GENERATED", GeneratedString)
}

func customVersionPrinter(c *cli.Context) {
	fmt.Printf("%v v=%v rev=%v d=%v\n", filepath.Base(c.App.Name),
		VersionString, RevisionString, GeneratedString)
}

// MaxCreateRetries is the number of times the "create" job will be retried.
const MaxCreateRetries = 10

// Core is used as a central manager for all Cloud Brain functionality. The HTTP
// API and the background workers are just frontends for the Core, and calls
// methods on Core for functionality.
type Core struct {
	db                database.DB
	redisPool         *redis.Pool
	redisWorkerPrefix string

	cloudProvidersMutex sync.Mutex
	cloudProviders      map[string]cloud.Provider
}

// NewCore is used to create a new Core backed by the given database and
// background Backend.
func NewCore(db database.DB, redisPool *redis.Pool, redisWorkerPrefix string) *Core {
	return &Core{
		db:                db,
		redisPool:         redisPool,
		redisWorkerPrefix: redisWorkerPrefix,
	}
}

// GetInstance gets the instance information stored in the database for a given
// instance ID.
func (c *Core) GetInstance(ctx context.Context, id string) (*Instance, error) {
	instance, err := c.db.GetInstance(id)

	if err == database.ErrInstanceNotFound {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	return &Instance{
		ID:           instance.ID,
		ProviderName: instance.ProviderName,
		Image:        instance.Image,
		State:        instance.State,
		IPAddress:    instance.IPAddress,
		UpstreamID:   instance.UpstreamID,
		ErrorReason:  instance.ErrorReason,
	}, nil
}

// CreateInstanceAttributes contains attributes needed to start an instance
type CreateInstanceAttributes struct {
	ImageName    string
	InstanceType string
	PublicSSHKey string
}

//DeleteInstanceAttributes contains attributes needed to delete an instance
type DeleteInstanceAttributes struct {
	InstanceID string
}

// CreateInstance creates an instance in the database and queues off the cloud
// create job in the background.
func (c *Core) CreateInstance(ctx context.Context, providerName string, attr CreateInstanceAttributes) (*Instance, error) {
	id, err := c.db.CreateInstance(database.Instance{
		ProviderName: providerName,
		Image:        attr.ImageName,
		InstanceType: attr.InstanceType,
		PublicSSHKey: attr.PublicSSHKey,
		State:        "creating",
	})
	if err != nil {
		return nil, errors.Wrap(err, "error creating instance in database")
	}

	var enqueuer = work.NewEnqueuer(c.redisWorkerPrefix, c.redisPool)

	_, err = enqueuer.Enqueue("create", work.Q{
		"context": ctx,
		"payload": []byte(id),
	})
	if err != nil {
		// TODO(henrikhodne): Delete the record in the database?
		return nil, errors.Wrap(err, "error enqueueing 'create' job in the background")
	}

	return &Instance{
		ID:           id,
		ProviderName: providerName,
		Image:        attr.ImageName,
		State:        "creating",
	}, nil
}

// RemoveInstance creates an instance in the database and queues off the cloud
// create job in the background.
func (c *Core) RemoveInstance(ctx context.Context, attr DeleteInstanceAttributes) error {
	inst, err := c.db.GetInstance(attr.InstanceID)
	if err != nil {
		return errors.Wrap(err, "error fetching instance from DB")
	}

	if inst.State == "terminating" || inst.State == "terminated" {
		return errors.Wrapf(err, "not removing instance, state is already %s", inst.State)
	}

	var enqueuer = work.NewEnqueuer(c.redisWorkerPrefix, c.redisPool)

	_, err = enqueuer.Enqueue("remove", work.Q{
		"context": ctx,
		"payload": []byte(attr.InstanceID),
	})

	if err != nil {
		return errors.Wrap(err, "error enqueueing 'remove' job in the background")
	}

	return nil
}

// ProviderCreateInstance is used to schedule the creation of the instance with
// the given ID on the provider selected for that instance.
func (c *Core) ProviderCreateInstance(ctx context.Context, byteID []byte) error {
	id := string(byteID)

	cbcontext.LoggerFromContext(ctx).WithFields(logrus.Fields{
		"instance_id": id,
	}).Info("creating instance")

	dbInstance, err := c.db.GetInstance(id)<commit_msg>adjust signature of core.ProviderCreateInstance to conform to gocraft/work<commit_after>package cloudbrain

import (
	"crypto/subtle"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/garyburd/redigo/redis"
	"github.com/gocraft/work"
	"github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
	"github.com/travis-ci/cloud-brain/cbcontext"
	"github.com/travis-ci/cloud-brain/cloud"
	"github.com/travis-ci/cloud-brain/database"
	"golang.org/x/crypto/scrypt"
	"golang.org/x/net/context"
	"gopkg.in/urfave/cli.v2"
)

var (
	//VersionString gets set during `make`
	VersionString = "?"
	//RevisionString gets set during `make`
	RevisionString = "?"
	//RevisionURLString gets set during `make`
	RevisionURLString = "?"
	//GeneratedString gets set during `make`
	GeneratedString = "?"
	//CopyrightString gets set during `make`
	CopyrightString = "?"
)

func init() {
	cli.VersionPrinter = customVersionPrinter
	_ = os.Setenv("VERSION", VersionString)
	_ = os.Setenv("REVISION", RevisionString)
	_ = os.Setenv("GENERATED", GeneratedString)
}

func customVersionPrinter(c *cli.Context) {
	fmt.Printf("%v v=%v rev=%v d=%v\n", filepath.Base(c.App.Name),
		VersionString, RevisionString, GeneratedString)
}

// MaxCreateRetries is the number of times the "create" job will be retried.
const MaxCreateRetries = 10

// Core is used as a central manager for all Cloud Brain functionality. The HTTP
// API and the background workers are just frontends for the Core, and calls
// methods on Core for functionality.
type Core struct {
	db                database.DB
	redisPool         *redis.Pool
	redisWorkerPrefix string

	cloudProvidersMutex sync.Mutex
	cloudProviders      map[string]cloud.Provider
}

// NewCore is used to create a new Core backed by the given database and
// background Backend.
func NewCore(db database.DB, redisPool *redis.Pool, redisWorkerPrefix string) *Core {
	return &Core{
		db:                db,
		redisPool:         redisPool,
		redisWorkerPrefix: redisWorkerPrefix,
	}
}

// GetInstance gets the instance information stored in the database for a given
// instance ID.
func (c *Core) GetInstance(ctx context.Context, id string) (*Instance, error) {
	instance, err := c.db.GetInstance(id)

	if err == database.ErrInstanceNotFound {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	return &Instance{
		ID:           instance.ID,
		ProviderName: instance.ProviderName,
		Image:        instance.Image,
		State:        instance.State,
		IPAddress:    instance.IPAddress,
		UpstreamID:   instance.UpstreamID,
		ErrorReason:  instance.ErrorReason,
	}, nil
}

// CreateInstanceAttributes contains attributes needed to start an instance
type CreateInstanceAttributes struct {
	ImageName    string
	InstanceType string
	PublicSSHKey string
}

//DeleteInstanceAttributes contains attributes needed to delete an instance
type DeleteInstanceAttributes struct {
	InstanceID string
}

// CreateInstance creates an instance in the database and queues off the cloud
// create job in the background.
func (c *Core) CreateInstance(ctx context.Context, providerName string, attr CreateInstanceAttributes) (*Instance, error) {
	id, err := c.db.CreateInstance(database.Instance{
		ProviderName: providerName,
		Image:        attr.ImageName,
		InstanceType: attr.InstanceType,
		PublicSSHKey: attr.PublicSSHKey,
		State:        "creating",
	})
	if err != nil {
		return nil, errors.Wrap(err, "error creating instance in database")
	}

	var enqueuer = work.NewEnqueuer(c.redisWorkerPrefix, c.redisPool)

	_, err = enqueuer.Enqueue("create", work.Q{
		"context": ctx,
		"payload": []byte(id),
	})
	if err != nil {
		// TODO(henrikhodne): Delete the record in the database?
		return nil, errors.Wrap(err, "error enqueueing 'create' job in the background")
	}

	return &Instance{
		ID:           id,
		ProviderName: providerName,
		Image:        attr.ImageName,
		State:        "creating",
	}, nil
}

// RemoveInstance creates an instance in the database and queues off the cloud
// create job in the background.
func (c *Core) RemoveInstance(ctx context.Context, attr DeleteInstanceAttributes) error {
	inst, err := c.db.GetInstance(attr.InstanceID)
	if err != nil {
		return errors.Wrap(err, "error fetching instance from DB")
	}

	if inst.State == "terminating" || inst.State == "terminated" {
		return errors.Wrapf(err, "not removing instance, state is already %s", inst.State)
	}

	var enqueuer = work.NewEnqueuer(c.redisWorkerPrefix, c.redisPool)

	_, err = enqueuer.Enqueue("remove", work.Q{
		"context": ctx,
		"payload": []byte(attr.InstanceID),
	})

	if err != nil {
		return errors.Wrap(err, "error enqueueing 'remove' job in the background")
	}

	return nil
}

// ProviderCreateInstance is used to schedule the creation of the instance with
// the given ID on the provider selected for that instance.
func (c *Core) ProviderCreateInstance(job *work.Job) error {
	ctx := job.Args["context"].(context.Context)
	byteID := job.Args["payload"].([]byte)

	id := string(byteID)

	cbcontext.LoggerFromContext(ctx).WithFields(logrus.Fields{
		"instance_id": id,
	}).Info("creating instance")

	dbInstance, err := c.db.GetInstance(id)
{'fraction_non_alphanumeric': 0.11249878036881647, 'fraction_numerical': 0.0005854229680944482, 'mean_word_length': 3.9951267056530213, 'pattern_counts': {'":': 10, '<': 3, '<?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': '4181613', 'n_tokens_mistral': 3370, 'n_tokens_neox': 3239, 'n_words': 928}