<commit_before>import "fmt"
func majorityElement(nums []int) []int {
    // 创建返回值
    var res = make([]int, 0)
    if nums == nil || len(nums) == 0 {
        return res
    }
    // 初始化两个候选人以及他们的计数票
    cand1 := nums[0]
    count1 := 0
    cand2 := nums[0]
    count2 := 0

    // 摩尔投票法
    // 配对阶段
    for _, num := range nums{
        // 投票
        if cand1 == num {
            count1++
            continue
        }
        if cand2 == num {
            count2++
            continue
        }

        if count1 == 0 {
            cand1 = num
            count1++
            continue
        }
        if cand2 == 0 {
            cand2 = num
            count2++
            continue
        }
        count1--
        count2--
    }
    // 计数阶段
    count1 = 0
    count2 = 0
    for _, num := range nums {
        if cand1 == num {
            count1 ++
        }
        if cand2 == num {
            count2 ++
        }
    }

    if count1 > len(nums)/3 {
        res = append(res, cand1)
    }

    if count2 > len(nums)/3 {
        res = append(res, cand2)
    }
    return res
}<commit_msg>fix: 修复error<commit_after>import "fmt"

func majorityElement(nums []int) []int {
    // 创建返回值
    var res = make([]int, 0)
    if nums == nil || len(nums) == 0 {
        return res
    }

    // 初始化两个候选人 candidate，以及他们的计数票
    cand1 := nums[0]
    count1 := 0
    cand2 := nums[0]
    count2 := 0

    //摩尔投票法
    // 配对阶段
    for _, num := range nums {
        // 投票
        if cand1 == num {
            count1++
            continue
        }
        if cand2 == num {
            count2++
            continue
        }

        if count1 == 0 {
            cand1 = num
            count1++
            continue
        }
        if count2 == 0 {
            cand2 = num
            count2++
            continue
        }

        count1--
        count2--
    }
    // 计数阶段
    count1 = 0
    count2 = 0
    for _, num := range nums {
        if cand1 == num {
            count1++
        } else if cand2 == num {
            count2++
        }
    }

    if count1 > len(nums)/3 {
        res = append(res, cand1)
    }
    if count2 > len(nums)/3 {
        res = append(res, cand2)
    }
    return res
}<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-sql"
	"github.com/flynn/flynn/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/flynn/flynn/controller/schema"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/flynn/pkg/postgres"
	"github.com/flynn/flynn/pkg/sse"
)

type formationKey struct {
	AppID, ReleaseID string
}

type FormationRepo struct {
	db        *postgres.DB
	apps      *AppRepo
	releases  *ReleaseRepo
	artifacts *ArtifactRepo

	subscriptions map[chan *ct.ExpandedFormation]struct{}
	stopListener  chan struct{}
	subMtx        sync.RWMutex
}

func NewFormationRepo(db *postgres.DB, appRepo *AppRepo, releaseRepo *ReleaseRepo, artifactRepo *ArtifactRepo) *FormationRepo {
	return &FormationRepo{
		db:            db,
		apps:          appRepo,
		releases:      releaseRepo,
		artifacts:     artifactRepo,
		subscriptions: make(map[chan *ct.ExpandedFormation]struct{}),
		stopListener:  make(chan struct{}),
	}
}

func (r *FormationRepo) validateFormProcs(f *ct.Formation) error {
	release, err := r.releases.Get(f.ReleaseID)
	if err != nil {
		return err
	}
	rel := release.(*ct.Release)
	invalid := make([]string, 0, len(f.Processes))
	for k := range f.Processes {
		if _, ok := rel.Processes[k]; !ok {
			invalid = append(invalid, k)
		}
	}
	if len(invalid) > 0 {
		return ct.ValidationError{Message: fmt.Sprintf("Requested formation includes process types that do not exist in release. Invalid process types: [%s]", strings.Join(invalid, ", "))}
	}
	return nil
}

func (r *FormationRepo) Add(f *ct.Formation) error {
	if err := r.validateFormProcs(f); err != nil {
		return err
	}
	tx, err := r.db.Begin()
	if err != nil {
		return err
	}
	procs, err := json.Marshal(f.Processes)
	if err != nil {
		return err
	}
	scale := &ct.Scale{
		Processes: f.Processes,
		ReleaseID: f.ReleaseID,
	}
	prevFormation, _ := r.Get(f.AppID, f.ReleaseID)
	if prevFormation != nil {
		scale.PrevProcesses = prevFormation.Processes
	}
	err = tx.QueryRow("INSERT INTO formations (app_id, release_id, processes) VALUES ($1, $2, $3) RETURNING created_at, updated_at",
		f.AppID, f.ReleaseID, procs).Scan(&f.CreatedAt, &f.UpdatedAt)
	if postgres.IsUniquenessError(err, "") {
		tx.Rollback()
		tx, err = r.db.Begin()
		if err != nil {
			return err
		}
		err = tx.QueryRow("UPDATE formations SET processes = $3, updated_at = now(), deleted_at = NULL WHERE app_id = $1 AND release_id = $2 RETURNING created_at, updated_at",
			f.AppID, f.ReleaseID, procs).Scan(&f.CreatedAt, &f.UpdatedAt)
	}
	if err != nil {
		tx.Rollback()
		return err
	}
	if err := createEvent(tx.Exec, &ct.Event{
		AppID:      f.AppID,
		ObjectID:   f.AppID + ":" + f.ReleaseID,
		ObjectType: ct.EventTypeScale,
	}, scale); err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit()
}

func scanFormation(s postgres.Scanner) (*ct.Formation, error) {
	f := &ct.Formation{}
	var procs []byte
	err := s.Scan(&f.AppID, &f.ReleaseID, &procs, &f.CreatedAt, &f.UpdatedAt)
	if err != nil {
		if err == sql.ErrNoRows {
			err = ErrNotFound
		}
		return nil, err
	}
	if len(procs) > 0 {
		err = json.Unmarshal(procs, &f.Processes)
	}
	return f, err
}

func (r *FormationRepo) Get(appID, releaseID string) (*ct.Formation, error) {
	row := r.db.QueryRow("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE app_id = $1 AND release_id = $2 AND deleted_at IS NULL", appID, releaseID)
	return scanFormation(row)
}

func (r *FormationRepo) List(appID string) ([]*ct.Formation, error) {
	rows, err := r.db.Query("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE app_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC", appID)
	if err != nil {
		return nil, err
	}
	formations := []*ct.Formation{}
	for rows.Next() {
		formation, err := scanFormation(rows)
		if err != nil {
			rows.Close()
			return nil, err
		}
		formations = append(formations, formation)
	}
	return formations, nil
}

func (r *FormationRepo) Remove(appID, releaseID string) error {
	tx, err := r.db.Begin()
	if err != nil {
		return err
	}
	scale := &ct.Scale{
		ReleaseID: releaseID,
	}
	prevFormation, _ := r.Get(appID, releaseID)
	if prevFormation != nil {
		scale.PrevProcesses = prevFormation.Processes
	}
	_, err = tx.Exec("UPDATE formations SET deleted_at = now(), processes = NULL, updated_at = now() WHERE app_id = $1 AND release_id = $2", appID, releaseID)
	if err != nil {
		tx.Rollback()
		return err
	}
	if err := createEvent(tx.Exec, &ct.Event{
		AppID:      appID,
		ObjectID:   appID + ":" + releaseID,
		ObjectType: ct.EventTypeScale,
	}, scale); err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit()
}

func (r *FormationRepo) publish(appID, releaseID string) {
	formation, err := r.Get(appID, releaseID)
	if err == ErrNotFound {
		// formation delete event
		updated_at := time.Now()
		formation = &ct.Formation{AppID: appID, ReleaseID: releaseID, UpdatedAt: &updated_at}
	} else if err != nil {
		// TODO: log error
		return
	}

	f, err := r.expandFormation(formation)
	if err != nil {
		// TODO: log error
		return
	}
	r.subMtx.RLock()
	defer r.subMtx.RUnlock()

	for ch := range r.subscriptions {
		ch <- f
	}
}

func (r *FormationRepo) expandFormation(formation *ct.Formation) (*ct.ExpandedFormation, error) {
	app, err := r.apps.Get(formation.AppID)
	if err == ErrNotFound {
		app = &ct.App{ID: formation.AppID}
	} else if err != nil {
		return nil, err
	}
	release, err := r.releases.Get(formation.ReleaseID)
	if err != nil {
		return nil, err
	}
	artifact, err := r.artifacts.Get(release.(*ct.Release).ArtifactID)
	if err != nil {
		return nil, err
	}
	f := &ct.ExpandedFormation{
		App:       app.(*ct.App),
		Release:   release.(*ct.Release),
		Artifact:  artifact.(*ct.Artifact),
		Processes: formation.Processes,
		UpdatedAt: *formation.UpdatedAt,
	}
	return f, nil
}

func (r *FormationRepo) startListener() error {
	log := logger.New("fn", "FormationRepo.startListener")
	listener, err := r.db.Listen("formations", log)
	if err != nil {
		return err
	}
	go func() {
		for {
			select {
			case n, ok := <-listener.Notify:
				if !ok {
					r.unsubscribeAll()
					return
				}
				ids := strings.SplitN(n.Extra, ":", 2)
				go r.publish(ids[0], ids[1])
			case <-r.stopListener:
				listener.Close()
				return
			}
		}
	}()
	return nil
}

func (r *FormationRepo) unsubscribeAll() {
	r.subMtx.Lock()
	defer r.subMtx.Unlock()

	for ch := range r.subscriptions {
		r.unsubscribeLocked(ch)
		close(ch)
	}
}

func (r *FormationRepo) Subscribe(ch chan *ct.ExpandedFormation, stopCh <-chan struct{}, since time.Time) error {
	var startListener bool
	r.subMtx.Lock()
	if len(r.subscriptions) == 0 {
		startListener = true
	}
	r.subscriptions[ch] = struct{}{}
	r.subMtx.Unlock()
	if startListener {
		if err := r.startListener(); err != nil {
			r.Unsubscribe(ch)
			return err
		}
	}
	go r.sendUpdatedSince(ch, stopCh, since)
	return nil
}

func (r *FormationRepo) sendUpdatedSince(ch chan *ct.ExpandedFormation, stopCh <-chan struct{}, since time.Time) error {
	rows, err := r.db.Query("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE updated_at >= $1 ORDER BY updated_at DESC", since)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		formation, err := scanFormation(rows)
		if err != nil {
			return err
		}
		ef, err := r.expandFormation(formation)
		if err != nil {
			return err
		}
		select {
		case ch <- ef:
		case <-stopCh:
			return nil
		}
	}
	ch <- &ct.ExpandedFormation{} // sentinel
	return rows.Err()
}

func (r *FormationRepo) Unsubscribe(ch chan *ct.ExpandedFormation) {
	r.subMtx.Lock()
	defer r.subMtx.Unlock()
	r.unsubscribeLocked(ch)
}

func (r *FormationRepo) unsubscribeLocked(ch chan *ct.ExpandedFormation) {
	go func() {
		// drain to prevent deadlock while removing the listener
		for range ch {
		}
	}()
	delete(r.subscriptions, ch)
	if len(r.subscriptions) == 0 {
		select {
		case r.stopListener <- struct{}{}:
		default:
		}
	}
}

func (c *controllerAPI) PutFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	app := c.getApp(ctx)
	release, err := c.getRelease(ctx)
	if err != nil {
		respondWithError(w, err)
		return
	}

	var formation ct.Formation
	if err = httphelper.DecodeJSON(req, &formation); err != nil {
		respondWithError(w, err)
		return
	}

	if release.ArtifactID == "" {
		respondWithError(w, ct.ValidationError{Message: "release is not deployable"})
		return
	}

	formation.AppID = app.ID
	formation.ReleaseID = release.ID

	if err = schema.Validate(formation); err != nil {
		respondWithError(w, err)
		return
	}

	if err = c.formationRepo.Add(&formation); err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, &formation)
}

func (c *controllerAPI) GetFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	app := c.getApp(ctx)
	formation, err := c.formationRepo.Get(app.ID, params.ByName("releases_id"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, formation)
}

func (c *controllerAPI) DeleteFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	app := c.getApp(ctx)
	formation, err := c.formationRepo.Get(app.ID, params.ByName("releases_id"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	err = c.formationRepo.Remove(app.ID, formation.ReleaseID)
	if err != nil {
		respondWithError(w, err)
		return
	}
	w.WriteHeader(200)
}

func (c *controllerAPI) ListFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	app := c.getApp(ctx)
	list, err := c.formationRepo.List(app.ID)
	if err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, list)
}

func (c *controllerAPI) GetFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	ch := make(chan *ct.ExpandedFormation)
	stopCh := make(chan struct{})
	since, err := time.Parse(time.RFC3339, req.FormValue("since"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	if err := c.formationRepo.Subscribe(ch, stopCh, since); err != nil {
		respondWithError(w, err)
		return
	}
	defer c.formationRepo.Unsubscribe(ch)
	defer close(stopCh)
	l, _ := ctxhelper.LoggerFromContext(ctx)
	sse.ServeStream(w, ch, l)
}
<commit_msg>controller: Fix formation stream race<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-sql"
	"github.com/flynn/flynn/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/flynn/flynn/controller/schema"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/flynn/pkg/postgres"
	"github.com/flynn/flynn/pkg/sse"
)

type formationKey struct {
	AppID, ReleaseID string
}

type FormationRepo struct {
	db        *postgres.DB
	apps      *AppRepo
	releases  *ReleaseRepo
	artifacts *ArtifactRepo

	subscriptions map[chan *ct.ExpandedFormation]struct{}
	stopListener  chan struct{}
	subMtx        sync.RWMutex
}

func NewFormationRepo(db *postgres.DB, appRepo *AppRepo, releaseRepo *ReleaseRepo, artifactRepo *ArtifactRepo) *FormationRepo {
	return &FormationRepo{
		db:            db,
		apps:          appRepo,
		releases:      releaseRepo,
		artifacts:     artifactRepo,
		subscriptions: make(map[chan *ct.ExpandedFormation]struct{}),
		stopListener:  make(chan struct{}),
	}
}

func (r *FormationRepo) validateFormProcs(f *ct.Formation) error {
	release, err := r.releases.Get(f.ReleaseID)
	if err != nil {
		return err
	}
	rel := release.(*ct.Release)
	invalid := make([]string, 0, len(f.Processes))
	for k := range f.Processes {
		if _, ok := rel.Processes[k]; !ok {
			invalid = append(invalid, k)
		}
	}
	if len(invalid) > 0 {
		return ct.ValidationError{Message: fmt.Sprintf("Requested formation includes process types that do not exist in release. Invalid process types: [%s]", strings.Join(invalid, ", "))}
	}
	return nil
}

func (r *FormationRepo) Add(f *ct.Formation) error {
	if err := r.validateFormProcs(f); err != nil {
		return err
	}
	tx, err := r.db.Begin()
	if err != nil {
		return err
	}
	procs, err := json.Marshal(f.Processes)
	if err != nil {
		return err
	}
	scale := &ct.Scale{
		Processes: f.Processes,
		ReleaseID: f.ReleaseID,
	}
	prevFormation, _ := r.Get(f.AppID, f.ReleaseID)
	if prevFormation != nil {
		scale.PrevProcesses = prevFormation.Processes
	}
	err = tx.QueryRow("INSERT INTO formations (app_id, release_id, processes) VALUES ($1, $2, $3) RETURNING created_at, updated_at",
		f.AppID, f.ReleaseID, procs).Scan(&f.CreatedAt, &f.UpdatedAt)
	if postgres.IsUniquenessError(err, "") {
		tx.Rollback()
		tx, err = r.db.Begin()
		if err != nil {
			return err
		}
		err = tx.QueryRow("UPDATE formations SET processes = $3, updated_at = now(), deleted_at = NULL WHERE app_id = $1 AND release_id = $2 RETURNING created_at, updated_at",
			f.AppID, f.ReleaseID, procs).Scan(&f.CreatedAt, &f.UpdatedAt)
	}
	if err != nil {
		tx.Rollback()
		return err
	}
	if err := createEvent(tx.Exec, &ct.Event{
		AppID:      f.AppID,
		ObjectID:   f.AppID + ":" + f.ReleaseID,
		ObjectType: ct.EventTypeScale,
	}, scale); err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit()
}

func scanFormation(s postgres.Scanner) (*ct.Formation, error) {
	f := &ct.Formation{}
	var procs []byte
	err := s.Scan(&f.AppID, &f.ReleaseID, &procs, &f.CreatedAt, &f.UpdatedAt)
	if err != nil {
		if err == sql.ErrNoRows {
			err = ErrNotFound
		}
		return nil, err
	}
	if len(procs) > 0 {
		err = json.Unmarshal(procs, &f.Processes)
	}
	return f, err
}

func (r *FormationRepo) Get(appID, releaseID string) (*ct.Formation, error) {
	row := r.db.QueryRow("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE app_id = $1 AND release_id = $2 AND deleted_at IS NULL", appID, releaseID)
	return scanFormation(row)
}

func (r *FormationRepo) List(appID string) ([]*ct.Formation, error) {
	rows, err := r.db.Query("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE app_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC", appID)
	if err != nil {
		return nil, err
	}
	formations := []*ct.Formation{}
	for rows.Next() {
		formation, err := scanFormation(rows)
		if err != nil {
			rows.Close()
			return nil, err
		}
		formations = append(formations, formation)
	}
	return formations, nil
}

func (r *FormationRepo) Remove(appID, releaseID string) error {
	tx, err := r.db.Begin()
	if err != nil {
		return err
	}
	scale := &ct.Scale{
		ReleaseID: releaseID,
	}
	prevFormation, _ := r.Get(appID, releaseID)
	if prevFormation != nil {
		scale.PrevProcesses = prevFormation.Processes
	}
	_, err = tx.Exec("UPDATE formations SET deleted_at = now(), processes = NULL, updated_at = now() WHERE app_id = $1 AND release_id = $2", appID, releaseID)
	if err != nil {
		tx.Rollback()
		return err
	}
	if err := createEvent(tx.Exec, &ct.Event{
		AppID:      appID,
		ObjectID:   appID + ":" + releaseID,
		ObjectType: ct.EventTypeScale,
	}, scale); err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit()
}

func (r *FormationRepo) publish(appID, releaseID string) {
	formation, err := r.Get(appID, releaseID)
	if err == ErrNotFound {
		// formation delete event
		updated_at := time.Now()
		formation = &ct.Formation{AppID: appID, ReleaseID: releaseID, UpdatedAt: &updated_at}
	} else if err != nil {
		// TODO: log error
		return
	}

	f, err := r.expandFormation(formation)
	if err != nil {
		// TODO: log error
		return
	}
	r.subMtx.RLock()
	defer r.subMtx.RUnlock()

	for ch := range r.subscriptions {
		ch <- f
	}
}

func (r *FormationRepo) expandFormation(formation *ct.Formation) (*ct.ExpandedFormation, error) {
	app, err := r.apps.Get(formation.AppID)
	if err == ErrNotFound {
		app = &ct.App{ID: formation.AppID}
	} else if err != nil {
		return nil, err
	}
	release, err := r.releases.Get(formation.ReleaseID)
	if err != nil {
		return nil, err
	}
	artifact, err := r.artifacts.Get(release.(*ct.Release).ArtifactID)
	if err != nil {
		return nil, err
	}
	f := &ct.ExpandedFormation{
		App:       app.(*ct.App),
		Release:   release.(*ct.Release),
		Artifact:  artifact.(*ct.Artifact),
		Processes: formation.Processes,
		UpdatedAt: *formation.UpdatedAt,
	}
	return f, nil
}

func (r *FormationRepo) startListener() error {
	log := logger.New("fn", "FormationRepo.startListener")
	listener, err := r.db.Listen("formations", log)
	if err != nil {
		return err
	}
	go func() {
		for {
			select {
			case n, ok := <-listener.Notify:
				if !ok {
					r.unsubscribeAll()
					return
				}
				ids := strings.SplitN(n.Extra, ":", 2)
				go r.publish(ids[0], ids[1])
			case <-r.stopListener:
				listener.Close()
				return
			}
		}
	}()
	return nil
}

func (r *FormationRepo) unsubscribeAll() {
	r.subMtx.Lock()
	defer r.subMtx.Unlock()

	for ch := range r.subscriptions {
		r.unsubscribeLocked(ch)
		close(ch)
	}
}

func (r *FormationRepo) Subscribe(ch chan *ct.ExpandedFormation, stopCh <-chan struct{}, since time.Time) error {
	// we need to keep the mutex locked whilst calling startListener
	// to avoid a race where multiple subscribers can get added to
	// r.subscriptions before a potentially failed listener start,
	// meaning subsequent subscribers wont try to start the listener.
	r.subMtx.Lock()
	defer r.subMtx.Unlock()

	if len(r.subscriptions) == 0 {
		if err := r.startListener(); err != nil {
			return err
		}
	}
	r.subscriptions[ch] = struct{}{}

	go r.sendUpdatedSince(ch, stopCh, since)
	return nil
}

func (r *FormationRepo) sendUpdatedSince(ch chan *ct.ExpandedFormation, stopCh <-chan struct{}, since time.Time) error {
	rows, err := r.db.Query("SELECT app_id, release_id, processes, created_at, updated_at FROM formations WHERE updated_at >= $1 ORDER BY updated_at DESC", since)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		formation, err := scanFormation(rows)
		if err != nil {
			return err
		}
		ef, err := r.expandFormation(formation)
		if err != nil {
			return err
		}
		select {
		case ch <- ef:
		case <-stopCh:
			return nil
		}
	}
	ch <- &ct.ExpandedFormation{} // sentinel
	return rows.Err()
}

func (r *FormationRepo) Unsubscribe(ch chan *ct.ExpandedFormation) {
	r.subMtx.Lock()
	defer r.subMtx.Unlock()
	r.unsubscribeLocked(ch)
}

func (r *FormationRepo) unsubscribeLocked(ch chan *ct.ExpandedFormation) {
	go func() {
		// drain to prevent deadlock while removing the listener
		for range ch {
		}
	}()
	delete(r.subscriptions, ch)
	if len(r.subscriptions) == 0 {
		select {
		case r.stopListener <- struct{}{}:
		default:
		}
	}
}

func (c *controllerAPI) PutFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	app := c.getApp(ctx)
	release, err := c.getRelease(ctx)
	if err != nil {
		respondWithError(w, err)
		return
	}

	var formation ct.Formation
	if err = httphelper.DecodeJSON(req, &formation); err != nil {
		respondWithError(w, err)
		return
	}

	if release.ArtifactID == "" {
		respondWithError(w, ct.ValidationError{Message: "release is not deployable"})
		return
	}

	formation.AppID = app.ID
	formation.ReleaseID = release.ID

	if err = schema.Validate(formation); err != nil {
		respondWithError(w, err)
		return
	}

	if err = c.formationRepo.Add(&formation); err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, &formation)
}

func (c *controllerAPI) GetFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	app := c.getApp(ctx)
	formation, err := c.formationRepo.Get(app.ID, params.ByName("releases_id"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, formation)
}

func (c *controllerAPI) DeleteFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	app := c.getApp(ctx)
	formation, err := c.formationRepo.Get(app.ID, params.ByName("releases_id"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	err = c.formationRepo.Remove(app.ID, formation.ReleaseID)
	if err != nil {
		respondWithError(w, err)
		return
	}
	w.WriteHeader(200)
}

func (c *controllerAPI) ListFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	app := c.getApp(ctx)
	list, err := c.formationRepo.List(app.ID)
	if err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, list)
}

func (c *controllerAPI) GetFormations(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	ch := make(chan *ct.ExpandedFormation)
	stopCh := make(chan struct{})
	since, err := time.Parse(time.RFC3339, req.FormValue("since"))
	if err != nil {
		respondWithError(w, err)
		return
	}
	if err := c.formationRepo.Subscribe(ch, stopCh, since); err != nil {
		respondWithError(w, err)
		return
	}
	defer c.formationRepo.Unsubscribe(ch)
	defer close(stopCh)
	l, _ := ctxhelper.LoggerFromContext(ctx)
	sse.ServeStream(w, ch, l)
}
<|endoftext|>


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

    https://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 orchestrator

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/google/gnxi/gnxi_tester/config"
	"github.com/google/go-cmp/cmp"
	"github.com/spf13/viper"
	"golang.org/x/net/context"
)

type clientCounter struct {
	CountContainerList,
	CountContainerStart,
	CountImageList,
	CountImageBuild,
	CountImagePull,
	CountContainerExecStart,
	CountContainerExecAttach,
	CountContainerExecCreate,
	CountContainerExecInspect,
	CountCopyToContainer,
	CountContainerRemove,
	CountImageRemove,
	CountContainerStop,
	CountContainerCreate int
}

type mockClient struct {
	Client
	clientCounter
	images     []types.ImageSummary
	containers []types.Container
	reader     *bufio.Reader
	code       int
}

type mockReader int

func (r mockReader) Read(p []byte) (n int, err error) {
	err = io.EOF
	return
}

func (r mockReader) Close() error {
	return nil
}

func (c *mockClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
	c.CountContainerList++
	return c.containers, nil
}

func (c *mockClient) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
	c.CountContainerStart++
	return nil
}

func (c *mockClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
	c.CountImageList++
	return c.images, nil
}

func (c *mockClient) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
	c.CountImageBuild++
	return types.ImageBuildResponse{Body: mockReader(0)}, nil
}

func (c *mockClient) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
	c.CountImagePull++
	return mockReader(0), nil
}

func (c *mockClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
	c.CountContainerCreate++
	return container.ContainerCreateCreatedBody{}, nil
}

func (c *mockClient) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error {
	c.CountContainerExecStart++
	return nil
}

func (c *mockClient) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) {
	c.CountContainerExecAttach++
	return types.HijackedResponse{
		Reader: c.reader,
		Conn:   &net.UnixConn{},
	}, nil
}

func (c *mockClient) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
	c.CountContainerExecCreate++
	return types.IDResponse{}, nil
}

func (c *mockClient) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
	c.CountContainerExecInspect++
	return types.ContainerExecInspect{ExitCode: c.code}, nil
}

func (c *mockClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
	c.CountCopyToContainer++
	return nil
}

func (c *mockClient) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
	c.CountContainerRemove++
	return nil
}

func (c *mockClient) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
	c.CountImageRemove++
	return []types.ImageDelete{}, nil
}

func (c *mockClient) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
	c.CountContainerStop++
	return nil
}

func TestInitContainer(t *testing.T) {
	tests := []struct {
		name  string
		names []string
		err   error
		buildImg,
		runtimeImg string
		images     []types.ImageSummary
		containers []types.Container
		counter    clientCounter
	}{
		{
			"container already running",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{
				{RepoTags: []string{"name"}},
				{RepoTags: []string{"build"}},
				{RepoTags: []string{"runtime"}},
			},
			[]types.Container{{Names: []string{"/name"}, State: "running"}},
			clientCounter{CountImageList: 2, CountContainerList: 1},
		},
		{
			"container not running",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{
				{RepoTags: []string{"name"}},
				{RepoTags: []string{"build"}},
				{RepoTags: []string{"runtime"}},
			},
			[]types.Container{{Names: []string{"/name"}}},
			clientCounter{CountImageList: 2, CountContainerList: 1, CountContainerStart: 1},
		},
		{
			"pull images and build",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{},
			[]types.Container{},
			clientCounter{CountImageList: 3, CountContainerList: 1, CountContainerStart: 1, CountImageBuild: 1, CountImagePull: 2, CountContainerCreate: 1},
		},
		{
			"no name",
			[]string{},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{},
			[]types.Container{},
			clientCounter{CountImageList: 2, CountContainerList: 1, CountImagePull: 2},
		},
	}
	tarFile = func(string, string) (io.ReadCloser, error) {
		return mockReader(1), nil
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			viper.Set("docker.build", test.buildImg)
			viper.Set("docker.runtime", test.runtimeImg)
			client := &mockClient{images: test.images, containers: test.containers}
			newClient = func() {
				dockerClient = client
			}
			if err := InitContainers(test.names); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("wanted error(%v), got(%v)", test.err, err)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}

func TestRunContainer(t *testing.T) {
	tests := []struct {
		name,
		containerName,
		args string
		counter     clientCounter
		containers  []types.Container
		err         error
		out         string
		code        int
		reader      *bufio.Reader
		insertFiles []string
	}{
		{
			"couldn't find container",
			"name",
			"",
			clientCounter{
				CountContainerList: 1,
			},
			[]types.Container{},
			errors.New("couldn't find container name"),
			"",
			0,
			&bufio.Reader{},
			[]string{},
		},
		{
			"run successfully",
			"name",
			"arg",
			clientCounter{
				CountContainerList:        1,
				CountContainerExecAttach:  1,
				CountContainerExecCreate:  1,
				CountContainerExecInspect: 1,
				CountCopyToContainer:      3,
			},
			[]types.Container{
				{Names: []string{"/name"}},
			},
			nil,
			"out",
			0,
			bufio.NewReader(bytes.NewBuffer([]byte{2, 0, 0, 0, 0, 0, 0, 3, 'o', 'u', 't'})),
			[]string{"ff"},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			tarFile = func(string, string) (io.ReadCloser, error) {
				return mockReader(1), nil
			}
			client := &mockClient{containers: test.containers, code: test.code, reader: test.reader}
			dockerClient = client
			out, code, err := RunContainer(test.containerName, test.args, &config.Device{}, test.insertFiles)
			if fmt.Sprintf("%v", test.err) != fmt.Sprintf("%v", err) {
				t.Errorf("wanted error(%v), got(%v)", test.err, err)
			}
			if test.out != out {
				t.Errorf("wanted out(%v), got(%v)", test.out, out)
			}
			if test.code != code {
				t.Errorf("wanted code(%d), got(%d)", test.code, code)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}

func TestWipeContainers(t *testing.T) {
	tests := []struct {
		name       string
		names      []string
		counter    clientCounter
		containers []types.Container
	}{
		{
			"no names",
			[]string{},
			clientCounter{},
			[]types.Container{},
		},
		{
			"2 names",
			[]string{"one", "two"},
			clientCounter{
				CountContainerList:   2,
				CountContainerRemove: 2,
				CountContainerStop:   2,
				CountImageRemove:     2,
			},
			[]types.Container{
				{Names: []string{"/one"}},
				{Names: []string{"/two"}},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			client := &mockClient{containers: test.containers}
			newClient = func() {
				dockerClient = client
			}
			if err := WipeContainers(test.names); err != nil {
				t.Errorf("Error in WipeContainers: %v", err)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}
<commit_msg>Capitalize test errors<commit_after>/* Copyright 2020 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

    https://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 orchestrator

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/google/gnxi/gnxi_tester/config"
	"github.com/google/go-cmp/cmp"
	"github.com/spf13/viper"
	"golang.org/x/net/context"
)

type clientCounter struct {
	CountContainerList,
	CountContainerStart,
	CountImageList,
	CountImageBuild,
	CountImagePull,
	CountContainerExecStart,
	CountContainerExecAttach,
	CountContainerExecCreate,
	CountContainerExecInspect,
	CountCopyToContainer,
	CountContainerRemove,
	CountImageRemove,
	CountContainerStop,
	CountContainerCreate int
}

type mockClient struct {
	Client
	clientCounter
	images     []types.ImageSummary
	containers []types.Container
	reader     *bufio.Reader
	code       int
}

type mockReader int

func (r mockReader) Read(p []byte) (n int, err error) {
	err = io.EOF
	return
}

func (r mockReader) Close() error {
	return nil
}

func (c *mockClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
	c.CountContainerList++
	return c.containers, nil
}

func (c *mockClient) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
	c.CountContainerStart++
	return nil
}

func (c *mockClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
	c.CountImageList++
	return c.images, nil
}

func (c *mockClient) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
	c.CountImageBuild++
	return types.ImageBuildResponse{Body: mockReader(0)}, nil
}

func (c *mockClient) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
	c.CountImagePull++
	return mockReader(0), nil
}

func (c *mockClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
	c.CountContainerCreate++
	return container.ContainerCreateCreatedBody{}, nil
}

func (c *mockClient) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error {
	c.CountContainerExecStart++
	return nil
}

func (c *mockClient) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) {
	c.CountContainerExecAttach++
	return types.HijackedResponse{
		Reader: c.reader,
		Conn:   &net.UnixConn{},
	}, nil
}

func (c *mockClient) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
	c.CountContainerExecCreate++
	return types.IDResponse{}, nil
}

func (c *mockClient) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
	c.CountContainerExecInspect++
	return types.ContainerExecInspect{ExitCode: c.code}, nil
}

func (c *mockClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
	c.CountCopyToContainer++
	return nil
}

func (c *mockClient) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
	c.CountContainerRemove++
	return nil
}

func (c *mockClient) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
	c.CountImageRemove++
	return []types.ImageDelete{}, nil
}

func (c *mockClient) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
	c.CountContainerStop++
	return nil
}

func TestInitContainer(t *testing.T) {
	tests := []struct {
		name  string
		names []string
		err   error
		buildImg,
		runtimeImg string
		images     []types.ImageSummary
		containers []types.Container
		counter    clientCounter
	}{
		{
			"container already running",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{
				{RepoTags: []string{"name"}},
				{RepoTags: []string{"build"}},
				{RepoTags: []string{"runtime"}},
			},
			[]types.Container{{Names: []string{"/name"}, State: "running"}},
			clientCounter{CountImageList: 2, CountContainerList: 1},
		},
		{
			"container not running",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{
				{RepoTags: []string{"name"}},
				{RepoTags: []string{"build"}},
				{RepoTags: []string{"runtime"}},
			},
			[]types.Container{{Names: []string{"/name"}}},
			clientCounter{CountImageList: 2, CountContainerList: 1, CountContainerStart: 1},
		},
		{
			"pull images and build",
			[]string{"name"},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{},
			[]types.Container{},
			clientCounter{CountImageList: 3, CountContainerList: 1, CountContainerStart: 1, CountImageBuild: 1, CountImagePull: 2, CountContainerCreate: 1},
		},
		{
			"no name",
			[]string{},
			nil,
			"build",
			"runtime",
			[]types.ImageSummary{},
			[]types.Container{},
			clientCounter{CountImageList: 2, CountContainerList: 1, CountImagePull: 2},
		},
	}
	tarFile = func(string, string) (io.ReadCloser, error) {
		return mockReader(1), nil
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			viper.Set("docker.build", test.buildImg)
			viper.Set("docker.runtime", test.runtimeImg)
			client := &mockClient{images: test.images, containers: test.containers}
			newClient = func() {
				dockerClient = client
			}
			if err := InitContainers(test.names); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("Wanted error(%v), got(%v)", test.err, err)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("Method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}

func TestRunContainer(t *testing.T) {
	tests := []struct {
		name,
		containerName,
		args string
		counter     clientCounter
		containers  []types.Container
		err         error
		out         string
		code        int
		reader      *bufio.Reader
		insertFiles []string
	}{
		{
			"couldn't find container",
			"name",
			"",
			clientCounter{
				CountContainerList: 1,
			},
			[]types.Container{},
			errors.New("couldn't find container name"),
			"",
			0,
			&bufio.Reader{},
			[]string{},
		},
		{
			"run successfully",
			"name",
			"arg",
			clientCounter{
				CountContainerList:        1,
				CountContainerExecAttach:  1,
				CountContainerExecCreate:  1,
				CountContainerExecInspect: 1,
				CountCopyToContainer:      3,
			},
			[]types.Container{
				{Names: []string{"/name"}},
			},
			nil,
			"out",
			0,
			bufio.NewReader(bytes.NewBuffer([]byte{2, 0, 0, 0, 0, 0, 0, 3, 'o', 'u', 't'})),
			[]string{"ff"},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			tarFile = func(string, string) (io.ReadCloser, error) {
				return mockReader(1), nil
			}
			client := &mockClient{containers: test.containers, code: test.code, reader: test.reader}
			dockerClient = client
			out, code, err := RunContainer(test.containerName, test.args, &config.Device{}, test.insertFiles)
			if fmt.Sprintf("%v", test.err) != fmt.Sprintf("%v", err) {
				t.Errorf("Wanted error(%v), got(%v)", test.err, err)
			}
			if test.out != out {
				t.Errorf("Wanted out(%v), got(%v)", test.out, out)
			}
			if test.code != code {
				t.Errorf("Wanted code(%d), got(%d)", test.code, code)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("Method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}

func TestWipeContainers(t *testing.T) {
	tests := []struct {
		name       string
		names      []string
		counter    clientCounter
		containers []types.Container
	}{
		{
			"no names",
			[]string{},
			clientCounter{},
			[]types.Container{},
		},
		{
			"2 names",
			[]string{"one", "two"},
			clientCounter{
				CountContainerList:   2,
				CountContainerRemove: 2,
				CountContainerStop:   2,
				CountImageRemove:     2,
			},
			[]types.Container{
				{Names: []string{"/one"}},
				{Names: []string{"/two"}},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			client := &mockClient{containers: test.containers}
			newClient = func() {
				dockerClient = client
			}
			if err := WipeContainers(test.names); err != nil {
				t.Errorf("Error in WipeContainers: %v", err)
			}
			if diff := cmp.Diff(test.counter, client.clientCounter); diff != "" {
				t.Errorf("Method call counter mismatch (-want +got): %s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
  "encoding/json"
  "fmt"
  "github.com/streadway/amqp"
  . "koding/db/models"
  "koding/tools/amqputil"
  "labix.org/v2/mgo/bson"
  "log"
  "strings"
)

type Status string

const (
  DELETE Status = "delete"
  MERGE  Status = "merge"
)

var (
  EXCHANGE_NAME     = "topicModifierExchange"
  WORKER_QUEUE_NAME = "topicModifierWorkerQueue"
)

type Consumer struct {
  conn    *amqp.Connection
  channel *amqp.Channel
  name    string
}

type TagModifierData struct {
  TagId  string `json:"tagId"`
  Status Status `json:"status"`
}

func init() {
  declareExchange()
}

func main() {
  log.Printf("Tag Modifier Worker Started")
  consumeMessages()
  // ack message
}

func declareExchange() {

  connection := amqputil.CreateConnection("exchangeDeclareConnection")

  channel := amqputil.CreateChannel(connection)

  err := channel.ExchangeDeclare(EXCHANGE_NAME, "fanout", true, false, false, false, nil)
  if err != nil {
    log.Println("exchange.declare: %s", err)
    panic(err)
  }

  //name, durable, autoDelete, exclusive, noWait, args Table
  _, err = channel.QueueDeclare(WORKER_QUEUE_NAME, true, false, false, false, nil)
  if err != nil {
    log.Println("queue.declare: %s", err)
    panic(err)
  }
  err = channel.QueueBind(WORKER_QUEUE_NAME, "", EXCHANGE_NAME, false, nil)
  if err != nil {
    log.Println("queue.bind: %s", err)
    panic(err)
  }

}

func consumeMessages() {
  c := &Consumer{
    conn:    nil,
    channel: nil,
    name:    "",
  }

  c.name = "tagModifier"
  c.conn = amqputil.CreateConnection(c.name)
  c.channel = amqputil.CreateChannel(c.conn)

  postData, err := c.channel.Consume(WORKER_QUEUE_NAME, c.name, false, false, false, false, nil)
  if err != nil {
    log.Printf("Consume error %s", err)
    panic(err)
  }

  for rawMsg := range postData {
    modifierData := &TagModifierData{}
    if err = json.Unmarshal([]byte(rawMsg.Body), modifierData); err != nil {
      log.Println("Wrong Post Format", err, rawMsg)
    }

    modifyMessage := func() {
      tagId := modifierData.TagId
      switch modifierData.Status {
      default:
        log.Println("Unknown modification status")
      case DELETE:
        deleteTags(tagId)
      case MERGE:
        mergeTags(tagId)
      }
      rawMsg.Ack(false)
    }

    modifyMessage()

  }
}

func deleteTags(tagId string) {
  log.Println("delete")
  if tag := FindTagById(tagId); (tag != Tag{}) {
    log.Println("Valid tag")
    rels := FindRelationships(bson.M{"targetId": bson.ObjectIdHex(tagId), "as": "tag"})
    updatePosts(rels, "")
    updateRelationships(rels, &Tag{})
  }
  //ack

}

func mergeTags(tagId string) {
  log.Println("merge")

  if tag := FindTagById(tagId); (tag != Tag{}) {
    log.Println("Valid tag")

    synonym := FindSynonym(tagId)
    tagRels := FindRelationships(bson.M{"targetId": bson.ObjectIdHex(tagId), "as": "tag"})
    if len(tagRels) > 0 {
      updatePosts(tagRels, synonym.Id.Hex())
      updateRelationships(tagRels, &synonym)
    }

    postRels := FindRelationships(bson.M{"sourceId": bson.ObjectIdHex(tagId), "as": "post"})
    if len(postRels) > 0 {
      updateRelationships(postRels, &synonym)
      updateCounts(&tag, &synonym)
    }
    UpdateTag(&synonym)
    tag.Counts = TagCount{} // reset counts
    UpdateTag(&tag)
    updateFollowers(&tag, &synonym)
  }
}

func updatePosts(rels []Relationship, newTagId string) {
  var newTag string
  if newTagId != "" {
    newTag = fmt.Sprintf("|#:JTag:%v|", newTagId)
  }

  for _, rel := range rels {
    tagId := rel.TargetId.Hex()
    post := FindPostWithId(rel.SourceId.Hex())
    modifiedTag := fmt.Sprintf("|#:JTag:%v|", tagId)
    post.Body = strings.Replace(post.Body, modifiedTag, newTag, -1)
    UpdatePost(&post)
  }
  log.Printf("%v Posts updated", len(rels))
}

func updateRelationships(rels []Relationship, synonym *Tag) {
  for _, rel := range rels {
    removeRelationship(rel)
    if (synonym != &Tag{}) {
      if rel.TargetName == "JTag" {
        rel.TargetId = synonym.Id
      } else {
        rel.SourceId = synonym.Id
      }
      rel.Id = bson.NewObjectId()
      createRelationship(rel)
    }
  }
}

func updateCounts(tag *Tag, synonym *Tag) {
  synonym.Counts.Following += tag.Counts.Following
  synonym.Counts.Followers += tag.Counts.Followers
  synonym.Counts.Post += tag.Counts.Post
  synonym.Counts.Tagged += tag.Counts.Tagged
}

func updateFollowers(tag *Tag, synonym *Tag) {
  var arr [2]bson.M
  arr[0] = bson.M{
    "targetId": tag.Id,
    "as":       "follower",
  }
  arr[1] = bson.M{
    "sourceId": tag.Id,
    "as":       "follower",
  }
  rels := FindRelationships(bson.M{"$or": arr})
  log.Printf("%v follower rels found", len(rels))
  if len(rels) > 0 {
    updateRelationships(rels, synonym)
  }
}

func createRelationship(relationship Relationship) {
  CreateGraphRelationship(relationship)
  CreateRelationship(relationship)
}

func removeRelationship(relationship Relationship) {
  RemoveGraphRelationship(relationship)
  RemoveRelationship(relationship)

}
<commit_msg>Moderation: Follower update method changed, for the cases where the users are already following the new topic (still a bit buggy)<commit_after>package main

import (
  "encoding/json"
  "fmt"
  "github.com/streadway/amqp"
  . "koding/db/models"
  "koding/tools/amqputil"
  "labix.org/v2/mgo/bson"
  "log"
  "strings"
)

type Status string

const (
  DELETE Status = "delete"
  MERGE  Status = "merge"
)

var (
  EXCHANGE_NAME     = "topicModifierExchange"
  WORKER_QUEUE_NAME = "topicModifierWorkerQueue"
)

type Consumer struct {
  conn    *amqp.Connection
  channel *amqp.Channel
  name    string
}

type TagModifierData struct {
  TagId  string `json:"tagId"`
  Status Status `json:"status"`
}

func init() {
  declareExchange()
}

func main() {
  log.Printf("Tag Modifier Worker Started")
  consumeMessages()
}

func declareExchange() {

  connection := amqputil.CreateConnection("exchangeDeclareConnection")

  channel := amqputil.CreateChannel(connection)

  err := channel.ExchangeDeclare(EXCHANGE_NAME, "fanout", true, false, false, false, nil)
  if err != nil {
    log.Println("exchange.declare: %s", err)
    panic(err)
  }

  //name, durable, autoDelete, exclusive, noWait, args Table
  _, err = channel.QueueDeclare(WORKER_QUEUE_NAME, true, false, false, false, nil)
  if err != nil {
    log.Println("queue.declare: %s", err)
    panic(err)
  }
  err = channel.QueueBind(WORKER_QUEUE_NAME, "", EXCHANGE_NAME, false, nil)
  if err != nil {
    log.Println("queue.bind: %s", err)
    panic(err)
  }

}

func consumeMessages() {
  c := &Consumer{
    conn:    nil,
    channel: nil,
    name:    "",
  }

  c.name = "tagModifier"
  c.conn = amqputil.CreateConnection(c.name)
  c.channel = amqputil.CreateChannel(c.conn)

  postData, err := c.channel.Consume(WORKER_QUEUE_NAME, c.name, false, false, false, false, nil)
  if err != nil {
    log.Printf("Consume error %s", err)
    panic(err)
  }

  for rawMsg := range postData {
    modifierData := &TagModifierData{}
    if err = json.Unmarshal([]byte(rawMsg.Body), modifierData); err != nil {
      log.Println("Wrong Post Format", err, rawMsg)
    }

    modifyMessage := func() {
      tagId := modifierData.TagId
      switch modifierData.Status {
      default:
        log.Println("Unknown modification status")
      case DELETE:
        deleteTags(tagId)
      case MERGE:
        mergeTags(tagId)
      }
      rawMsg.Ack(false)
    }

    modifyMessage()

  }
}

func deleteTags(tagId string) {
  log.Println("delete")
  if tag := FindTagById(tagId); (tag != Tag{}) {
    log.Println("Valid tag")
    rels := FindRelationships(bson.M{"targetId": bson.ObjectIdHex(tagId), "as": "tag"})
    updatePosts(rels, "")
    updateRelationships(rels, &Tag{})
  }
}

func mergeTags(tagId string) {
  log.Println("merge")

  if tag := FindTagById(tagId); (tag != Tag{}) {
    log.Println("Valid tag")

    synonym := FindSynonym(tagId)
    tagRels := FindRelationships(bson.M{"targetId": bson.ObjectIdHex(tagId), "as": "tag"})
    if len(tagRels) > 0 {
      updatePosts(tagRels, synonym.Id.Hex())
      updateRelationships(tagRels, &synonym)
    }

    postRels := FindRelationships(bson.M{"sourceId": bson.ObjectIdHex(tagId), "as": "post"})
    if len(postRels) > 0 {
      updateRelationships(postRels, &synonym)
      updateCounts(&tag, &synonym)
    }
    synonym.Counts.Followers += updateFollowers(&tag, &synonym)
    UpdateTag(&synonym)
    tag.Counts = TagCount{} // reset counts
    UpdateTag(&tag)
  }
}

func updatePosts(rels []Relationship, newTagId string) {
  var newTag string
  if newTagId != "" {
    newTag = fmt.Sprintf("|#:JTag:%v|", newTagId)
  }

  for _, rel := range rels {
    tagId := rel.TargetId.Hex()
    post := FindPostWithId(rel.SourceId.Hex())
    modifiedTag := fmt.Sprintf("|#:JTag:%v|", tagId)
    post.Body = strings.Replace(post.Body, modifiedTag, newTag, -1)
    UpdatePost(&post)
  }
  log.Printf("%v Posts updated", len(rels))
}

func updateRelationships(rels []Relationship, synonym *Tag) {
  for _, rel := range rels {
    removeRelationship(rel)
    if (synonym != &Tag{}) {
      if rel.TargetName == "JTag" {
        rel.TargetId = synonym.Id
      } else {
        rel.SourceId = synonym.Id
      }
      rel.Id = bson.NewObjectId()
      createRelationship(rel)
    }
  }
}

func updateCounts(tag *Tag, synonym *Tag) {
  synonym.Counts.Following += tag.Counts.Following // does this have any meaning?
  // synonym.Counts.Followers += tag.Counts.Followers
  synonym.Counts.Post += tag.Counts.Post
  synonym.Counts.Tagged += tag.Counts.Tagged
}

func updateFollowers(tag *Tag, synonym *Tag) int {
  var arr [2]bson.M
  arr[0] = bson.M{
    "targetId": tag.Id,
    "as":       "follower",
  }
  arr[1] = bson.M{
    "sourceId": tag.Id,
    "as":       "follower",
  }
  rels := FindRelationships(bson.M{"$or": arr})
  var removeRels []Relationship
  var updateRels []Relationship

  for _, rel := range rels {
    query := bson.M{
      "as": "follower",
    }

    if rel.SourceName == "JTag" {
      query["sourceId"] = synonym.Id
      query["targetId"] = rel.TargetId
    } else {
      query["sourceId"] = rel.SourceId
      query["targetId"] = synonym.Id
    }
    // for filtering already existing relationships
    _, err := FindRelationship(query)

    // what if there is really an error
    if err == nil {
      updateRels = append(updateRels, rel)
    } else {
      removeRels = append(removeRels, rel)
    }
  }

  log.Printf("%v users are already following new topic", len(removeRels))
  if len(removeRels) > 0 {
    updateRelationships(removeRels, &Tag{})
  }
  log.Printf("%v users followed new topic", len(updateRels))
  if len(updateRels) > 0 {
    updateRelationships(updateRels, synonym)
  }

  return len(updateRels)
}

func createRelationship(relationship Relationship) {
  CreateGraphRelationship(relationship)
  CreateRelationship(relationship)
}

func removeRelationship(relationship Relationship) {
  RemoveGraphRelationship(relationship)
  RemoveRelationship(relationship)

}
<|endoftext|>


<commit_before>/*
   Copyright 2017 Shlomi Noach, GitHub 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 kv

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	consulapi "github.com/armon/consul-api"
	"github.com/github/orchestrator/go/config"
	"github.com/openark/golib/log"
)

// A Consul store based on config's `ConsulAddress` and `ConsulKVPrefix`
type consulStore struct {
	client                        *consulapi.Client
	lastPairsDistributionStart    time.Time
	pairsDistributionSuccess      map[string]time.Time
	pairsDistributionSuccessMutex sync.Mutex
	distributionReentry           int64
}

// NewConsulStore creates a new consul store. It is possible that the client for this store is nil,
// which is the case if no consul config is provided.
func NewConsulStore() KVStore {
	store := &consulStore{
		lastPairsDistributionStart: time.Now(),
		pairsDistributionSuccess:   make(map[string]time.Time),
	}

	if config.Config.ConsulAddress != "" {
		consulConfig := consulapi.DefaultConfig()
		consulConfig.Address = config.Config.ConsulAddress
		// ConsulAclToken defaults to ""
		consulConfig.Token = config.Config.ConsulAclToken
		if client, err := consulapi.NewClient(consulConfig); err != nil {
			log.Errore(err)
		} else {
			store.client = client
		}
	}
	return store
}

func (this *consulStore) PutKeyValue(key string, value string) (err error) {
	if this.client == nil {
		return nil
	}
	pair := &consulapi.KVPair{Key: key, Value: []byte(value)}
	_, err = this.client.KV().Put(pair, nil)
	return err
}

func (this *consulStore) GetKeyValue(key string) (value string, found bool, err error) {
	if this.client == nil {
		return value, found, nil
	}
	pair, _, err := this.client.KV().Get(key, nil)
	if err != nil {
		return value, found, err
	}
	return string(pair.Value), (pair != nil), nil
}

func (this *consulStore) getPairsDistributionSuccessTime(key string) time.Time {
	this.pairsDistributionSuccessMutex.Lock()
	defer this.pairsDistributionSuccessMutex.Unlock()

	return this.pairsDistributionSuccess[key]
}

func (this *consulStore) markPairsDistributionSuccessTime(key string) {
	this.pairsDistributionSuccessMutex.Lock()
	defer this.pairsDistributionSuccessMutex.Unlock()

	this.pairsDistributionSuccess[key] = time.Now()
}

func (this *consulStore) DistributePairs(canonicalPairs [](*KVPair), fullPairs [](*KVPair)) (err error) {
	// This function is non re-entrant (it can only be running once at any point in time)
	if atomic.CompareAndSwapInt64(&this.distributionReentry, 0, 1) {
		log.Debugf("consulStore.DistributePairs(): entry")
		defer atomic.StoreInt64(&this.distributionReentry, 0)
	} else {
		log.Debugf("consulStore.DistributePairs(): non-reentrant")
		return
	}

	log.Debugf("consulStore.DistributePairs(): %d pairs canonical, %d pairs full", len(canonicalPairs), len(fullPairs))
	if !config.Config.ConsulCrossDataCenterDistribution {
		log.Debugf("consulStore.DistributePairs(): !config.Config.ConsulCrossDataCenterDistribution")
		return nil
	}

	previousPairsDistributionStart := this.lastPairsDistributionStart
	this.lastPairsDistributionStart = time.Now()

	datacenters, err := this.client.Catalog().Datacenters()
	if err != nil {
		return err
	}
	log.Debugf("consulStore.DistributePairs(): %d datacenters", len(datacenters))
	consulPairs := [](*consulapi.KVPair){}
	for _, kvPair := range fullPairs {
		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
		consulPairs = append(consulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
	}
	var wg sync.WaitGroup
	for _, datacenter := range datacenters {
		wg.Add(1)
		go func() {
			defer wg.Done()
			log.Debugf("consulStore.DistributePairs():datacenter: %+v", datacenter)
			writeOptions := &consulapi.WriteOptions{Datacenter: datacenter}

			for _, consulPair := range consulPairs {
				pairsDistributionKey := fmt.Sprintf("%s;%s:%s", datacenter, consulPair.Key, consulPair.Value)
				if lastSuccess := this.getPairsDistributionSuccessTime(pairsDistributionKey); !lastSuccess.Before(previousPairsDistributionStart) {
					log.Errorf("consulStore.DistributePairs(): skipping %s", pairsDistributionKey)
					continue
				}
				log.Errorf("consulStore.DistributePairs(): writing %s", pairsDistributionKey)
				if _, e := this.client.KV().Put(consulPair, writeOptions); e != nil {
					log.Errorf("consulStore.DistributePairs(): failed %s", pairsDistributionKey)
					err = e
				} else {
					log.Errorf("consulStore.DistributePairs(): success %s", pairsDistributionKey)
					this.markPairsDistributionSuccessTime(pairsDistributionKey)
				}
			}
		}()
	}
	wg.Wait()
	return err
}

//
// func (this *consulStore) DistributePairs(canonicalPairs [](*KVPair), fullPairs [](*KVPair)) (err error) {
// 	log.Debugf("consulStore.DistributePairs(): %d pairs canonical, %d pairs full", len(canonicalPairs), len(fullPairs))
// 	if !config.Config.ConsulCrossDataCenterDistribution {
// 		log.Debugf("consulStore.DistributePairs(): !config.Config.ConsulCrossDataCenterDistribution")
// 		return nil
// 	}
//
// 	previousPairsDistributionStart := this.lastPairsDistributionStart
// 	this.lastPairsDistributionStart = time.Now()
//
// 	datacenters, err := this.client.Catalog().Datacenters()
// 	if err != nil {
// 		return err
// 	}
// 	log.Debugf("consulStore.DistributePairs(): %d datacenters", len(datacenters))
// 	canonicalConsulPairs := [](*consulapi.KVPair){}
// 	for _, kvPair := range canonicalPairs {
// 		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
// 		canonicalConsulPairs = append(canonicalConsulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
// 	}
// 	fullConsulPairs := [](*consulapi.KVPair){}
// 	for _, kvPair := range fullPairs {
// 		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
// 		fullConsulPairs = append(fullConsulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
// 	}
// 	for _, datacenter := range datacenters {
// 		consulPairs := canonicalConsulPairs
// 		if lastSuccess := this.pairsDistributionSuccess[datacenter]; lastSuccess.Before(previousPairsDistributionStart) {
// 			// default value is time.Zero
// 			// No sign of success
// 			consulPairs = fullConsulPairs
// 			log.Debugf("consulStore.DistributePairs(): full pairs for datacenter: %+v", datacenter)
// 		} else {
// 			log.Debugf("consulStore.DistributePairs(): canonical pairs for datacenter: %+v", datacenter)
// 		}
//
// 		dataCenterErrorFound := false
// 		log.Debugf("consulStore.DistributePairs():datacenter: %+v", datacenter)
// 		writeOptions := &consulapi.WriteOptions{Datacenter: datacenter}
// 		for _, consulPair := range consulPairs {
// 			if _, e := this.client.KV().Put(consulPair, writeOptions); e != nil {
// 				dataCenterErrorFound = true
// 				log.Errorf("consulStore.DistributePairs(): failed writing %s", datacenter)
// 				err = e
// 			}
// 		}
// 		if !dataCenterErrorFound {
// 			log.Errorf("consulStore.DistributePairs(): %d keys written successfully on %s", len(consulPairs), datacenter)
// 			this.pairsDistributionSuccess[datacenter] = time.Now()
// 		}
// 	}
// 	return err
// }
<commit_msg>simpler use of cache<commit_after>/*
   Copyright 2017 Shlomi Noach, GitHub 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 kv

import (
	"fmt"
	"sync"
	"sync/atomic"

	"github.com/github/orchestrator/go/config"

	consulapi "github.com/armon/consul-api"
	"github.com/patrickmn/go-cache"

	"github.com/openark/golib/log"
)

// A Consul store based on config's `ConsulAddress` and `ConsulKVPrefix`
type consulStore struct {
	client                        *consulapi.Client
	kvCache                       *cache.Cache
	pairsDistributionSuccessMutex sync.Mutex
	distributionReentry           int64
}

// NewConsulStore creates a new consul store. It is possible that the client for this store is nil,
// which is the case if no consul config is provided.
func NewConsulStore() KVStore {
	store := &consulStore{
		kvCache: cache.New(cache.NoExpiration, cache.DefaultExpiration),
	}

	if config.Config.ConsulAddress != "" {
		consulConfig := consulapi.DefaultConfig()
		consulConfig.Address = config.Config.ConsulAddress
		// ConsulAclToken defaults to ""
		consulConfig.Token = config.Config.ConsulAclToken
		if client, err := consulapi.NewClient(consulConfig); err != nil {
			log.Errore(err)
		} else {
			store.client = client
		}
	}
	return store
}

func (this *consulStore) PutKeyValue(key string, value string) (err error) {
	if this.client == nil {
		return nil
	}
	pair := &consulapi.KVPair{Key: key, Value: []byte(value)}
	_, err = this.client.KV().Put(pair, nil)
	return err
}

func (this *consulStore) GetKeyValue(key string) (value string, found bool, err error) {
	if this.client == nil {
		return value, found, nil
	}
	pair, _, err := this.client.KV().Get(key, nil)
	if err != nil {
		return value, found, err
	}
	return string(pair.Value), (pair != nil), nil
}

func (this *consulStore) DistributePairs(canonicalPairs [](*KVPair), fullPairs [](*KVPair)) (err error) {
	// This function is non re-entrant (it can only be running once at any point in time)
	if atomic.CompareAndSwapInt64(&this.distributionReentry, 0, 1) {
		log.Debugf("consulStore.DistributePairs(): entry")
		defer atomic.StoreInt64(&this.distributionReentry, 0)
	} else {
		log.Debugf("consulStore.DistributePairs(): non-reentrant")
		return
	}

	log.Debugf("consulStore.DistributePairs(): %d pairs canonical, %d pairs full", len(canonicalPairs), len(fullPairs))
	if !config.Config.ConsulCrossDataCenterDistribution {
		log.Debugf("consulStore.DistributePairs(): !config.Config.ConsulCrossDataCenterDistribution")
		return nil
	}

	datacenters, err := this.client.Catalog().Datacenters()
	if err != nil {
		return err
	}
	log.Debugf("consulStore.DistributePairs(): %d datacenters", len(datacenters))
	consulPairs := [](*consulapi.KVPair){}
	for _, kvPair := range fullPairs {
		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
		consulPairs = append(consulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
	}
	var wg sync.WaitGroup
	for _, datacenter := range datacenters {
		wg.Add(1)
		go func() {
			defer wg.Done()
			log.Debugf("consulStore.DistributePairs():datacenter: %+v", datacenter)
			writeOptions := &consulapi.WriteOptions{Datacenter: datacenter}

			for _, consulPair := range consulPairs {
				val := string(consulPair.Value)
				kcCacheKey := fmt.Sprintf("%s;%s", datacenter, consulPair.Key)

				if value, found := this.kvCache.Get(kcCacheKey); found && val == value {
					log.Debugf("consulStore.DistributePairs(): skipping %s", kcCacheKey)
					continue
				}
				log.Debugf("consulStore.DistributePairs(): writing %s", kcCacheKey)
				if _, e := this.client.KV().Put(consulPair, writeOptions); e != nil {
					log.Errorf("consulStore.DistributePairs(): failed %s", kcCacheKey)
					err = e
				} else {
					log.Debugf("consulStore.DistributePairs(): success %s", kcCacheKey)
					this.kvCache.SetDefault(kcCacheKey, val)
				}
			}
		}()
	}
	wg.Wait()
	return err
}

//
// func (this *consulStore) DistributePairs(canonicalPairs [](*KVPair), fullPairs [](*KVPair)) (err error) {
// 	log.Debugf("consulStore.DistributePairs(): %d pairs canonical, %d pairs full", len(canonicalPairs), len(fullPairs))
// 	if !config.Config.ConsulCrossDataCenterDistribution {
// 		log.Debugf("consulStore.DistributePairs(): !config.Config.ConsulCrossDataCenterDistribution")
// 		return nil
// 	}
//
// 	previousPairsDistributionStart := this.lastPairsDistributionStart
// 	this.lastPairsDistributionStart = time.Now()
//
// 	datacenters, err := this.client.Catalog().Datacenters()
// 	if err != nil {
// 		return err
// 	}
// 	log.Debugf("consulStore.DistributePairs(): %d datacenters", len(datacenters))
// 	canonicalConsulPairs := [](*consulapi.KVPair){}
// 	for _, kvPair := range canonicalPairs {
// 		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
// 		canonicalConsulPairs = append(canonicalConsulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
// 	}
// 	fullConsulPairs := [](*consulapi.KVPair){}
// 	for _, kvPair := range fullPairs {
// 		log.Debugf("consulStore.DistributePairs():kvpair: %+v", kvPair)
// 		fullConsulPairs = append(fullConsulPairs, &consulapi.KVPair{Key: kvPair.Key, Value: []byte(kvPair.Value)})
// 	}
// 	for _, datacenter := range datacenters {
// 		consulPairs := canonicalConsulPairs
// 		if lastSuccess := this.pairsDistributionSuccess[datacenter]; lastSuccess.Before(previousPairsDistributionStart) {
// 			// default value is time.Zero
// 			// No sign of success
// 			consulPairs = fullConsulPairs
// 			log.Debugf("consulStore.DistributePairs(): full pairs for datacenter: %+v", datacenter)
// 		} else {
// 			log.Debugf("consulStore.DistributePairs(): canonical pairs for datacenter: %+v", datacenter)
// 		}
//
// 		dataCenterErrorFound := false
// 		log.Debugf("consulStore.DistributePairs():datacenter: %+v", datacenter)
// 		writeOptions := &consulapi.WriteOptions{Datacenter: datacenter}
// 		for _, consulPair := range consulPairs {
// 			if _, e := this.client.KV().Put(consulPair, writeOptions); e != nil {
// 				dataCenterErrorFound = true
// 				log.Errorf("consulStore.DistributePairs(): failed writing %s", datacenter)
// 				err = e
// 			}
// 		}
// 		if !dataCenterErrorFound {
// 			log.Errorf("consulStore.DistributePairs(): %d keys written successfully on %s", len(consulPairs), datacenter)
// 			this.pairsDistributionSuccess[datacenter] = time.Now()
// 		}
// 	}
// 	return err
// }
<|endoftext|>


<commit_before>package nexmo

import (
	"testing"
)

const (
	API_KEY           = "YOUR NEXMO API KEY GOES HERE"
	API_SECRET        = "YOUR NEXMO API SECRET GOES HERE"
	TEST_PHONE_NUMBER = "YOUR PHONE NUMBER GOES HERE"
)

func TestNexmoCreation(t *testing.T) {
	_, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}
}

func TestGetAccountBalance(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	balance, err := nexmo.GetBalance()
	if err != nil {
		t.Error("Failed to get account balance with error:", err)
	}

	t.Log("Got account balance: ", balance, "€")
}

func TestSendTextMessage(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	messageResponse, err := nexmo.SendTextMessage("go-nexmo", "00358123412345", "Looks like go-nexmo works great, we should definitely buy that njern guy a beer!", "001", false)
	if err != nil {
		t.Error("Failed to send text message with error:", err)
	}

	t.Log("Sent SMS, response was: ", messageResponse)
}

func TestFlashMessage(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	messageResponse, err := nexmo.SendFlashMessage("go-nexmo", "00358123412345", "Looks like go-nexmo works great, we should definitely buy that njern guy a beer!", "001", false)
	if err != nil {
		t.Error("Failed to send flash message (class 0 SMS) with error:", err)
	}

	t.Log("Sent Flash SMS, response was: ", messageResponse)
}
<commit_msg>Use enviroment variables to store testing credentials<commit_after>package nexmo

import (
	"fmt"
	"os"
	"testing"
)

var (
	API_KEY, API_SECRET, TEST_PHONE_NUMBER string
)

func init() {
	API_KEY = os.Getenv("NEXMO_KEY")
	if API_KEY == "" {
		fmt.Println("No API key specified. Please set NEXMO_KEY")
		os.Exit(1)
	}

	API_SECRET = os.Getenv("NEXMO_SECRET")
	if API_SECRET == "" {
		fmt.Println("No API secret specified. Please set NEXMO_SECRET")
		os.Exit(1)
	}

	TEST_PHONE_NUMBER = os.Getenv("NEXMO_NUM")
	if TEST_PHONE_NUMBER == "" {
		fmt.Println("No test phone number specified. Please set NEXMO_NUM")
		os.Exit(1)
	}
}

func TestNexmoCreation(t *testing.T) {
	_, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}
}

func TestGetAccountBalance(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	balance, err := nexmo.GetBalance()
	if err != nil {
		t.Error("Failed to get account balance with error:", err)
	}

	t.Log("Got account balance: ", balance, "€")
}

func TestSendTextMessage(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	messageResponse, err := nexmo.SendTextMessage("go-nexmo", "00358123412345", "Looks like go-nexmo works great, we should definitely buy that njern guy a beer!", "001", false)
	if err != nil {
		t.Error("Failed to send text message with error:", err)
	}

	t.Log("Sent SMS, response was: ", messageResponse)
}

func TestFlashMessage(t *testing.T) {
	nexmo, err := NexmoWithKeyAndSecret(API_KEY, API_SECRET)
	if err != nil {
		t.Error("Failed to create Nexmo object with error:", err)
	}

	messageResponse, err := nexmo.SendFlashMessage("go-nexmo", "00358123412345", "Looks like go-nexmo works great, we should definitely buy that njern guy a beer!", "001", false)
	if err != nil {
		t.Error("Failed to send flash message (class 0 SMS) with error:", err)
	}

	t.Log("Sent Flash SMS, response was: ", messageResponse)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"github.com/krasoffski/goplts/gopl/ch10/decompress"
	_ "github.com/krasoffski/goplts/gopl/ch10/decompress/zip"
)

func main() {
	src := flag.String("src", "", "path for archive")
	dst := flag.String("dst", "./", "destination directory, empty string '' is analogue of /dev/null")
	verbose := flag.Bool("verbose", true, "verbose output")
	flag.Parse()

	if *src == "" {
		flag.Usage()
		os.Exit(1)
	}

	f, err := os.Open(*src)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	entries, err := decompress.NewReader(f)
	if err != nil {
		log.Fatal(err)
	}
	for _, e := range entries {
		path := filepath.Join(*dst, e.Name)
		if *verbose {
			fmt.Println(e.Name)
		}

		if e.IsDir {
			// NOTE: think how to beautify such approach.
			if *dst == "" {
				continue
			}

			err := os.MkdirAll(path, e.Mode)
			if err != nil {
				log.Fatal(err)
			}
			e.ReaderCloser.Close()
			continue
		}

		if *dst == "" {
			io.Copy(ioutil.Discard, e.ReaderCloser)
			continue
		}

		if err := write(path, e); err != nil {
			log.Fatalf("save error: %v", err)
		}
	}
}

func write(path string, e *decompress.Entry) error {
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, e.Mode)
	if err != nil {
		return err
	}
	defer f.Close()
	defer e.ReaderCloser.Close()
	if _, err = io.Copy(f, e.ReaderCloser); err != nil {
		return err
	}
	return nil
}
<commit_msg>[10.2] error handling for discard copy<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"github.com/krasoffski/goplts/gopl/ch10/decompress"
	_ "github.com/krasoffski/goplts/gopl/ch10/decompress/zip"
)

func main() {
	src := flag.String("src", "", "path for archive")
	dst := flag.String("dst", "./", "destination directory, empty string '' is analogue of /dev/null")
	verbose := flag.Bool("verbose", true, "verbose output")
	flag.Parse()

	if *src == "" {
		flag.Usage()
		os.Exit(1)
	}

	f, err := os.Open(*src)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	entries, err := decompress.NewReader(f)
	if err != nil {
		log.Fatal(err)
	}
	for _, e := range entries {
		path := filepath.Join(*dst, e.Name)
		if *verbose {
			fmt.Println(e.Name)
		}

		if e.IsDir {
			// NOTE: think how to beautify such approach.
			if *dst == "" {
				continue
			}

			err := os.MkdirAll(path, e.Mode)
			if err != nil {
				log.Fatal(err)
			}
			e.ReaderCloser.Close()
			continue
		}

		// NOTE: think how to beautify such approach.
		if *dst == "" {
			if _, err := io.Copy(ioutil.Discard, e.ReaderCloser); err != nil {
				log.Fatalf("save error: %v", err)
			}
		} else {
			if err := write(path, e); err != nil {
				log.Fatalf("save error: %v", err)
			}
		}
	}
}

func write(path string, e *decompress.Entry) error {
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, e.Mode)
	if err != nil {
		return err
	}
	defer f.Close()
	defer e.ReaderCloser.Close()
	if _, err = io.Copy(f, e.ReaderCloser); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package qmd

import (
	"errors"
	"log"
	"os"
	"path"
	"path/filepath"
	"sync"
	"time"
)

type Scripts struct {
	Running bool

	sync.Mutex                   // guards files
	files      map[string]string // Map of scripts names to the actual files.
}

// Watch watches a specific directory for files and updates the cache.
func (s *Scripts) Watch(dir string) {
	for {
		info, err := os.Stat(dir)
		if err != nil {
			log.Printf("script_dir=\"" + dir + "\": " + err.Error())
		}
		if !info.IsDir() {
			log.Printf("script_dir=\"" + dir + "\": not a directory")
		}

		err = s.Walk(dir)
		if err != nil {
			log.Print(err)
		}

		time.Sleep(10 * time.Second)
	}
}

// Walk walks the ScriptDir and finds all the shell scripts.
func (s *Scripts) Walk(dir string) error {
	//log.Println("Scripts: Walking script_dir..")

	files := map[string]string{}
	if err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			if path.Ext(file) == ".sh" {
				rel, err := filepath.Rel(dir, file)
				if err != nil {
					return err
				}
				files[rel] = file
				//log.Println("Scripts: Found:", rel)
			}
		}
		return nil
	}); err != nil {
		return err
	}

	s.Lock()
	defer s.Unlock()

	s.files = files
	return nil
}

func (s *Scripts) Get(file string) (string, error) {
	s.Lock()
	defer s.Unlock()

	script, ok := s.files[file]
	if !ok {
		return "", errors.New("script doesn't exist")
	}
	return script, nil
}
<commit_msg>Fix panic on non-existing script_dir<commit_after>package qmd

import (
	"errors"
	"log"
	"os"
	"path"
	"path/filepath"
	"sync"
	"time"
)

type Scripts struct {
	Running bool

	sync.Mutex                   // guards files
	files      map[string]string // Map of scripts names to the actual files.
}

// Watch watches a specific directory for files and updates the cache.
func (s *Scripts) Watch(dir string) {
	for {
		info, err := os.Stat(dir)
		if err != nil {
			log.Printf("script_dir=\"" + dir + "\": no such directory")
			time.Sleep(1 * time.Second)
			continue
		}
		if !info.IsDir() {
			log.Printf("script_dir=\"" + dir + "\": not a directory")
			time.Sleep(1 * time.Second)
			continue
		}

		err = s.Walk(dir)
		if err != nil {
			log.Print(err)
			time.Sleep(1 * time.Second)
			continue
		}

		time.Sleep(10 * time.Second)
	}
}

// Walk walks the ScriptDir and finds all the shell scripts.
func (s *Scripts) Walk(dir string) error {
	files := map[string]string{}
	if err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			if path.Ext(file) == ".sh" {
				rel, err := filepath.Rel(dir, file)
				if err != nil {
					return err
				}
				files[rel] = file
			}
		}
		return nil
	}); err != nil {
		return err
	}

	s.Lock()
	defer s.Unlock()

	s.files = files
	return nil
}

func (s *Scripts) Get(file string) (string, error) {
	s.Lock()
	defer s.Unlock()

	script, ok := s.files[file]
	if !ok {
		return "", errors.New("script doesn't exist")
	}
	return script, nil
}
<|endoftext|>


<commit_before>/*
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"errors"
	"github.com/golang/glog"
	"github.com/mitchellh/mapstructure"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	v3tokens "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
)

type identity struct {
	scV3 *gophercloud.ServiceClient
}

type identityConfig struct {
	endpoint        string
	serviceUserName string
	servicePassword string
}

// Project holds project information extracted from the keystone response.
type Project struct {
	ID   string `mapstructure:"id"`
	Name string `mapstructure:"name"`
}

// RoleEntry contains the name of a role extracted from the keystone response.
type RoleEntry struct {
	Name string `mapstructure:"name"`
}

// Roles contains a list of role names extracted from the keystone response.
type Roles struct {
	Entries []RoleEntry
}

// Endpoint contains endpoint information extracted from the keystone response.
type Endpoint struct {
	ID        string `mapstructure:"id"`
	Region    string `mapstructure:"region"`
	Interface string `mapstructure:"interface"`
	URL       string `mapstructure:"url"`
}

// ServiceEntry contains information about a service extracted from the keystone response.
type ServiceEntry struct {
	ID        string     `mapstructure:"id"`
	Name      string     `mapstructure:"name"`
	Type      string     `mapstructure:"type"`
	Endpoints []Endpoint `mapstructure:"endpoints"`
}

// Services is a list of ServiceEntry structs
// These structs contain information about the services keystone knows about.
type Services struct {
	Entries []ServiceEntry
}

type getResult struct {
	v3tokens.GetResult
}

// extractProject
// Ideally we would actually contribute this functionality
// back to the gophercloud project, but for now we extend
// their object to allow us to get project information out
// of the response from the GET token validation request.
func (r getResult) extractProject() (*Project, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	// can there be more than one project?  You need to test.
	var response struct {
		Token struct {
			ValidProject Project `mapstructure:"project"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.V(2).Info(err)
		return nil, err
	}

	return &Project{
		ID:   response.Token.ValidProject.ID,
		Name: response.Token.ValidProject.Name,
	}, nil
}

func (r getResult) extractServices() (*Services, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	var response struct {
		Token struct {
			Entries []ServiceEntry `mapstructure:"catalog"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.Errorf(err.Error())
		return nil, err
	}

	return &Services{Entries: response.Token.Entries}, nil
}

// extractRole
// Ideally we would actually contribute this functionality
// back to the gophercloud project, but for now we extend
// their object to allow us to get project information out
// of the response from the GET token validation request.
func (r getResult) extractRoles() (*Roles, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	var response struct {
		Token struct {
			ValidRoles []RoleEntry `mapstructure:"roles"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.V(2).Info(err)
		return nil, err
	}

	return &Roles{Entries: response.Token.ValidRoles}, nil
}

// validate
// Confirm that the token has access to the Project they are requesting
// an operation on, and that they have a role which permits them to
// access this api.
func (i *identity) validate(token string, tenantID string, role string) bool {
	r := v3tokens.Get(i.scV3, token)

	result := getResult{r}

	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if p.ID != tenantID {
		glog.V(2).Info("expected ", tenantID, " got ", p.ID)
		return false
	}

	roles, err := result.extractRoles()
	if err != nil {
		return false
	}

	for i := range roles.Entries {
		if roles.Entries[i].Name == role {
			return true
		}
	}

	return false
}

// validateServices
// Validates that a given user belonging to a tenant
// can access a service specified by its type and name.
func (i *identity) validateService(token string, tenantID string, serviceType string, serviceName string) bool {
	r := v3tokens.Get(i.scV3, token)
	result := getResult{r}

	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if p.ID != tenantID {
		glog.Errorf("expected %s got %s\n", tenantID, p.ID)
		return false
	}

	services, err := result.extractServices()
	if err != nil {
		return false
	}

	for _, e := range services.Entries {
		if e.Type == serviceType {
			if serviceName == "" {
				return true
			}

			if e.Name == serviceName {
				return true
			}
		}
	}

	return false
}

func (i *identity) validateProjectRole(token string, project string, role string) bool {
	r := v3tokens.Get(i.scV3, token)
	result := getResult{r}
	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if project != "" && p.Name != project {
		return false
	}

	roles, err := result.extractRoles()
	if err != nil {
		return false
	}

	for i := range roles.Entries {
		if roles.Entries[i].Name == role {
			return true
		}
	}
	return false
}

func newIdentityClient(config identityConfig) (id *identity, err error) {
	opt := gophercloud.AuthOptions{
		IdentityEndpoint: config.endpoint + "/v3/",
		Username:         config.serviceUserName,
		Password:         config.servicePassword,
		TenantName:       "service",
		DomainID:         "default",
		AllowReauth:      false,
	}
	provider, err := openstack.AuthenticatedClient(opt)
	if err != nil {
		return
	}

	provider.ReauthFunc = func() error {
		provider.TokenID = ""
		return openstack.Authenticate(provider, opt)
	}

	v3client := openstack.NewIdentityV3(provider)
	if v3client == nil {
		return nil, errors.New("Unable to get keystone V3 client")
	}

	id = &identity{
		scV3: v3client,
	}
	return
}
<commit_msg>ciao-controller: identity: trivial cleanup<commit_after>/*
// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"errors"
	"github.com/golang/glog"
	"github.com/mitchellh/mapstructure"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	v3tokens "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
)

type identity struct {
	scV3 *gophercloud.ServiceClient
}

type identityConfig struct {
	endpoint        string
	serviceUserName string
	servicePassword string
}

// Project holds project information extracted from the keystone response.
type Project struct {
	ID   string `mapstructure:"id"`
	Name string `mapstructure:"name"`
}

// RoleEntry contains the name of a role extracted from the keystone response.
type RoleEntry struct {
	Name string `mapstructure:"name"`
}

// Roles contains a list of role names extracted from the keystone response.
type Roles struct {
	Entries []RoleEntry
}

// Endpoint contains endpoint information extracted from the keystone response.
type Endpoint struct {
	ID        string `mapstructure:"id"`
	Region    string `mapstructure:"region"`
	Interface string `mapstructure:"interface"`
	URL       string `mapstructure:"url"`
}

// ServiceEntry contains information about a service extracted from the keystone response.
type ServiceEntry struct {
	ID        string     `mapstructure:"id"`
	Name      string     `mapstructure:"name"`
	Type      string     `mapstructure:"type"`
	Endpoints []Endpoint `mapstructure:"endpoints"`
}

// Services is a list of ServiceEntry structs
// These structs contain information about the services keystone knows about.
type Services struct {
	Entries []ServiceEntry
}

type getResult struct {
	v3tokens.GetResult
}

// extractProject
// Ideally we would actually contribute this functionality
// back to the gophercloud project, but for now we extend
// their object to allow us to get project information out
// of the response from the GET token validation request.
func (r getResult) extractProject() (*Project, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	// can there be more than one project?  You need to test.
	var response struct {
		Token struct {
			ValidProject Project `mapstructure:"project"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.V(2).Info(err)
		return nil, err
	}

	return &Project{
		ID:   response.Token.ValidProject.ID,
		Name: response.Token.ValidProject.Name,
	}, nil
}

func (r getResult) extractServices() (*Services, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	var response struct {
		Token struct {
			Entries []ServiceEntry `mapstructure:"catalog"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.Errorf(err.Error())
		return nil, err
	}

	return &Services{Entries: response.Token.Entries}, nil
}

// extractRole
// Ideally we would actually contribute this functionality
// back to the gophercloud project, but for now we extend
// their object to allow us to get project information out
// of the response from the GET token validation request.
func (r getResult) extractRoles() (*Roles, error) {
	if r.Err != nil {
		glog.V(2).Info(r.Err)
		return nil, r.Err
	}

	var response struct {
		Token struct {
			ValidRoles []RoleEntry `mapstructure:"roles"`
		} `mapstructure:"token"`
	}

	err := mapstructure.Decode(r.Body, &response)
	if err != nil {
		glog.V(2).Info(err)
		return nil, err
	}

	return &Roles{Entries: response.Token.ValidRoles}, nil
}

// validate
// Confirm that the token has access to the Project they are requesting
// an operation on, and that they have a role which permits them to
// access this api.
func (i *identity) validate(token string, tenantID string, role string) bool {
	r := v3tokens.Get(i.scV3, token)

	result := getResult{r}

	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if p.ID != tenantID {
		glog.V(2).Info("expected ", tenantID, " got ", p.ID)
		return false
	}

	roles, err := result.extractRoles()
	if err != nil {
		return false
	}

	for i := range roles.Entries {
		if roles.Entries[i].Name == role {
			return true
		}
	}

	return false
}

// validateServices
// Validates that a given user belonging to a tenant
// can access a service specified by its type and name.
func (i *identity) validateService(token string, tenantID string, serviceType string, serviceName string) bool {
	r := v3tokens.Get(i.scV3, token)
	result := getResult{r}

	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if p.ID != tenantID {
		glog.Errorf("expected %s got %s\n", tenantID, p.ID)
		return false
	}

	services, err := result.extractServices()
	if err != nil {
		return false
	}

	for _, e := range services.Entries {
		if e.Type == serviceType {
			if serviceName == "" {
				return true
			}

			if e.Name == serviceName {
				return true
			}
		}
	}

	return false
}

func (i *identity) validateProjectRole(token string, project string, role string) bool {
	r := v3tokens.Get(i.scV3, token)
	result := getResult{r}
	p, err := result.extractProject()
	if err != nil {
		return false
	}

	if project != "" && p.Name != project {
		return false
	}

	roles, err := result.extractRoles()
	if err != nil {
		return false
	}

	for i := range roles.Entries {
		if roles.Entries[i].Name == role {
			return true
		}
	}
	return false
}

func newIdentityClient(config identityConfig) (*identity, error) {
	opt := gophercloud.AuthOptions{
		IdentityEndpoint: config.endpoint + "/v3/",
		Username:         config.serviceUserName,
		Password:         config.servicePassword,
		TenantName:       "service",
		DomainID:         "default",
		AllowReauth:      false,
	}
	provider, err := openstack.AuthenticatedClient(opt)
	if err != nil {
		return nil, err
	}

	provider.ReauthFunc = func() error {
		provider.TokenID = ""
		return openstack.Authenticate(provider, opt)
	}

	v3client := openstack.NewIdentityV3(provider)
	if v3client == nil {
		return nil, errors.New("Unable to get keystone V3 client")
	}

	id := &identity{
		scV3: v3client,
	}

	return id, err
}
<|endoftext|>