示例#1
0
文件: event.go 项目: tsuru/tsuru
func (l *lockUpdater) spin() {
	set := map[Target]struct{}{}
	for {
		select {
		case added := <-l.addCh:
			set[*added] = struct{}{}
		case removed := <-l.removeCh:
			delete(set, *removed)
		case <-l.stopCh:
			return
		case <-time.After(lockUpdateInterval):
		}
		conn, err := db.Conn()
		if err != nil {
			log.Errorf("[events] [lock update] error getting db conn: %s", err)
			continue
		}
		coll := conn.Events()
		slice := make([]interface{}, len(set))
		i := 0
		for id := range set {
			slice[i], _ = id.GetBSON()
			i++
		}
		err = coll.Update(bson.M{"_id": bson.M{"$in": slice}}, bson.M{"$set": bson.M{"lockupdatetime": time.Now().UTC()}})
		if err != nil && err != mgo.ErrNotFound {
			log.Errorf("[events] [lock update] error updating: %s", err)
		}
		conn.Close()
	}
}
示例#2
0
func (s *WriterSuite) SetUpSuite(c *gocheck.C) {
	var err error
	config.Set("database:url", "127.0.0.1:27017")
	config.Set("database:name", "tsuru_api_writer_test")
	s.conn, err = db.Conn()
	c.Assert(err, gocheck.IsNil)
}
示例#3
0
// UnbindUnit makes the unbind between the service instance and an unit.
func (si *ServiceInstance) UnbindUnit(app bind.App, unit bind.Unit) error {
	endpoint, err := si.Service().getClient("production")
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	updateOp := bson.M{"$pull": bson.M{"units": unit.GetID()}}
	err = conn.ServiceInstances().Update(bson.M{"name": si.Name, "service_name": si.ServiceName, "units": unit.GetID()}, updateOp)
	if err != nil {
		if err == mgo.ErrNotFound {
			return ErrUnitNotBound
		}
		return err
	}
	err = endpoint.UnbindUnit(si, app, unit)
	if err != nil {
		rollbackErr := si.update(bson.M{"$addToSet": bson.M{"units": unit.GetID()}})
		if rollbackErr != nil {
			log.Errorf("[unbind unit] could not add bound unit back to db after failure: %s", rollbackErr)
		}
		return err
	}
	return nil
}
示例#4
0
文件: app.go 项目: Zapelini/tsuru
// Same as AcquireApplicationLock but it keeps trying to acquire the lock
// until timeout is reached.
func AcquireApplicationLockWait(appName string, owner string, reason string, timeout time.Duration) (bool, error) {
	timeoutChan := time.After(timeout)
	conn, err := db.Conn()
	if err != nil {
		return false, err
	}
	defer conn.Close()
	for {
		appLock := AppLock{
			Locked:      true,
			Reason:      reason,
			Owner:       owner,
			AcquireDate: time.Now().In(time.UTC),
		}
		err = conn.Apps().Update(bson.M{"name": appName, "lock.locked": bson.M{"$in": []interface{}{false, nil}}}, bson.M{"$set": bson.M{"lock": appLock}})
		if err == nil {
			return true, nil
		}
		if err != mgo.ErrNotFound {
			return false, err
		}
		select {
		case <-timeoutChan:
			return false, nil
		case <-time.After(300 * time.Millisecond):
		}
	}
}
示例#5
0
文件: app.go 项目: Zapelini/tsuru
// setEnvsToApp adds environment variables to an app, serializing the resulting
// list of environment variables in all units of apps. This method can
// serialize them directly or using a queue.
//
// Besides the slice of environment variables, this method also takes two other
// parameters: publicOnly indicates whether only public variables can be
// overridden (if set to false, setEnvsToApp may override a private variable).
//
// shouldRestart defines if the server should be restarted after saving vars.
func (app *App) setEnvsToApp(envs []bind.EnvVar, publicOnly, shouldRestart bool, w io.Writer) error {
	if len(envs) == 0 {
		return nil
	}
	if w != nil {
		fmt.Fprintf(w, "---- Setting %d new environment variables ----\n", len(envs))
	}
	for _, env := range envs {
		set := true
		if publicOnly {
			e, err := app.getEnv(env.Name)
			if err == nil && !e.Public && e.InstanceName != "" {
				set = false
			}
		}
		if set {
			app.setEnv(env)
		}
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$set": bson.M{"env": app.Env}})
	if err != nil {
		return err
	}
	if !shouldRestart {
		return nil
	}
	return Provisioner.Restart(app, "", w)
}
示例#6
0
文件: app.go 项目: Zapelini/tsuru
// AddCName adds a CName to app. It updates the attribute,
// calls the SetCName function on the provisioner and saves
// the app in the database, returning an error when it cannot save the change
// in the database or add the CName on the provisioner.
func (app *App) AddCName(cnames ...string) error {
	for _, cname := range cnames {
		if cname != "" && !cnameRegexp.MatchString(cname) {
			return stderr.New("Invalid cname")
		}
		if cnameExists(cname) {
			return stderr.New("cname already exists!")
		}
		if s, ok := Provisioner.(provision.CNameManager); ok {
			if err := s.SetCName(app, cname); err != nil {
				return err
			}
		}
		conn, err := db.Conn()
		if err != nil {
			return err
		}
		defer conn.Close()
		app.CName = append(app.CName, cname)
		err = conn.Apps().Update(
			bson.M{"name": app.Name},
			bson.M{"$push": bson.M{"cname": cname}},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
示例#7
0
文件: app.go 项目: Zapelini/tsuru
// List returns the list of apps that the given user has access to.
//
// If the user does not have access to any app, this function returns an empty
// list and a nil error.
//
// The list can be filtered through the filter parameter.
func List(u *auth.User, filter *Filter) ([]App, error) {
	var apps []App
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	query := filter.Query()
	if u == nil || u.IsAdmin() {
		if err = conn.Apps().Find(query).All(&apps); err != nil {
			return []App{}, err
		}
		return apps, nil
	}
	ts, err := u.Teams()
	if err != nil {
		return []App{}, err
	}
	teams := auth.GetTeamsNames(ts)
	query["teams"] = bson.M{"$in": teams}
	if err := conn.Apps().Find(query).All(&apps); err != nil {
		return []App{}, err
	}
	return apps, nil
}
示例#8
0
文件: quota.go 项目: ningjh/tsuru
// ReserveApp reserves an app for the user, reserving it in the database. It's
// used to reserve the app in the user quota, returning an error when there
// isn't any space available.
func ReserveApp(user *User) error {
	user, err := checkUser(user.Email)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Users().Update(
		bson.M{"email": user.Email, "quota.inuse": user.InUse},
		bson.M{"$inc": bson.M{"quota.inuse": 1}},
	)
	for err == mgo.ErrNotFound {
		user, err = checkUser(user.Email)
		if err != nil {
			return err
		}
		err = conn.Users().Update(
			bson.M{"email": user.Email, "quota.inuse": user.InUse},
			bson.M{"$inc": bson.M{"quota.inuse": 1}},
		)
	}
	return err
}
示例#9
0
文件: quota.go 项目: ningjh/tsuru
// ReleaseApp releases an app from the user list, releasing the quota spot for
// another app.
func ReleaseApp(user *User) error {
	errCantRelease := errors.New("Cannot release unreserved app")
	user, err := GetUserByEmail(user.Email)
	if err != nil {
		return err
	}
	if user.Quota.InUse == 0 {
		return errCantRelease
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Users().Update(
		bson.M{"email": user.Email, "quota.inuse": user.InUse},
		bson.M{"$inc": bson.M{"quota.inuse": -1}},
	)
	for err == mgo.ErrNotFound {
		user, err = GetUserByEmail(user.Email)
		if err != nil {
			return err
		}
		if user.Quota.InUse == 0 {
			return errCantRelease
		}
		err = conn.Users().Update(
			bson.M{"email": user.Email, "quota.inuse": user.InUse},
			bson.M{"$inc": bson.M{"quota.inuse": -1}},
		)
	}
	return err
}
示例#10
0
func serviceDelete(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	u, err := t.User()
	if err != nil {
		return err
	}
	rec.Log(u.Email, "delete-service", r.URL.Query().Get(":name"))
	s, err := getServiceByOwner(r.URL.Query().Get(":name"), u)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	n, err := conn.ServiceInstances().Find(bson.M{"service_name": s.Name}).Count()
	if err != nil {
		return err
	}
	if n > 0 {
		msg := "This service cannot be removed because it has instances.\nPlease remove these instances before removing the service."
		return &errors.HTTP{Code: http.StatusForbidden, Message: msg}
	}
	err = s.Delete()
	if err != nil {
		return err
	}
	w.WriteHeader(http.StatusNoContent)
	return nil
}
示例#11
0
func revokeServiceAccess(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	u, err := t.User()
	if err != nil {
		return err
	}
	serviceName := r.URL.Query().Get(":service")
	teamName := r.URL.Query().Get(":team")
	rec.Log(u.Email, "revoke-service-access", "service="+serviceName, "team="+teamName)
	service, team, err := getServiceAndTeam(serviceName, teamName, u)
	if err != nil {
		return err
	}
	if len(service.Teams) < 2 {
		msg := "You can not revoke the access from this team, because it is the unique team with access to this service, and a service can not be orphaned"
		return &errors.HTTP{Code: http.StatusForbidden, Message: msg}
	}
	err = service.RevokeAccess(team)
	if err != nil {
		return &errors.HTTP{Code: http.StatusNotFound, Message: err.Error()}
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	return conn.Services().Update(bson.M{"_id": service.Name}, service)
}
示例#12
0
func (s *S) TestContainerRemoveStopsContainer(c *check.C) {
	conn, err := db.Conn()
	c.Assert(err, check.IsNil)
	defer conn.Close()
	a := app.App{Name: "test-app"}
	err = conn.Apps().Insert(a)
	c.Assert(err, check.IsNil)
	container, err := s.newContainer(newContainerOpts{AppName: a.Name}, nil)
	c.Assert(err, check.IsNil)
	defer s.removeTestContainer(container)
	err = container.Start(&StartArgs{Provisioner: s.p, App: &a})
	c.Assert(err, check.IsNil)
	err = container.Remove(s.p)
	c.Assert(err, check.IsNil)
	coll := s.p.Collection()
	defer coll.Close()
	err = coll.Find(bson.M{"id": container.ID}).One(&container)
	c.Assert(err, check.NotNil)
	c.Assert(err.Error(), check.Equals, "not found")
	client, _ := docker.NewClient(s.server.URL())
	_, err = client.InspectContainer(container.ID)
	c.Assert(err, check.NotNil)
	_, ok := err.(*docker.NoSuchContainer)
	c.Assert(ok, check.Equals, true)
}
示例#13
0
文件: user.go 项目: pedrosnk/tsuru
func (u *User) AddRole(roleName string, contextValue string) error {
	_, err := permission.FindRole(roleName)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Users().Update(bson.M{"email": u.Email}, bson.M{
		"$addToSet": bson.M{
			// Order matters in $addToSet, that's why bson.D is used instead
			// of bson.M.
			"roles": bson.D([]bson.DocElem{
				{Name: "name", Value: roleName},
				{Name: "contextvalue", Value: contextValue},
			}),
		},
	})
	if err != nil {
		return err
	}
	return u.Reload()
}
示例#14
0
文件: user.go 项目: pedrosnk/tsuru
func (u *User) Create() error {
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	if u.Quota.Limit == 0 {
		u.Quota = quota.Unlimited
		var limit int
		if limit, err = config.GetInt("quota:apps-per-user"); err == nil && limit > -1 {
			u.Quota.Limit = limit
		}
	}
	err = conn.Users().Insert(u)
	if err != nil {
		return err
	}
	err = u.createOnRepositoryManager()
	if err != nil {
		u.Delete()
		return err
	}
	err = u.AddRolesForEvent(permission.RoleEventUserCreate, "")
	if err != nil {
		log.Errorf("unable to add default roles during user creation for %q: %s", u.Email, err)
	}
	return nil
}
示例#15
0
文件: bs.go 项目: reoring/tsuru
func collection() (*storage.Collection, error) {
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	return conn.Collection("bsconfig"), nil
}
示例#16
0
func (s *PlatformSuite) TestPlatformUpdateShouldSetUpdatePlatformFlagOnApps(c *gocheck.C) {
	provisioner := testing.ExtensibleFakeProvisioner{
		FakeProvisioner: testing.NewFakeProvisioner(),
	}
	Provisioner = &provisioner
	defer func() {
		Provisioner = s.provisioner
	}()
	conn, err := db.Conn()
	c.Assert(err, gocheck.IsNil)
	defer conn.Close()
	name := "test_platform_update"
	args := make(map[string]string)
	args["dockerfile"] = "http://localhost/Dockerfile"
	err = PlatformAdd(name, nil, nil)
	c.Assert(err, gocheck.IsNil)
	defer conn.Platforms().Remove(bson.M{"_id": name})
	appName := "test_app"
	app := App{
		Name:     appName,
		Platform: name,
	}
	err = conn.Apps().Insert(app)
	c.Assert(err, gocheck.IsNil)
	defer conn.Apps().Remove(bson.M{"_id": appName})
	err = PlatformUpdate(name, args, nil)
	c.Assert(err, gocheck.IsNil)
	a, err := GetByName(appName)
	c.Assert(err, gocheck.IsNil)
	c.Assert(a.UpdatePlatform, gocheck.Equals, true)
}
示例#17
0
文件: app.go 项目: Zapelini/tsuru
func (app *App) unsetEnvsToApp(variableNames []string, publicOnly, shouldRestart bool, w io.Writer) error {
	if len(variableNames) == 0 {
		return nil
	}
	if w != nil {
		fmt.Fprintf(w, "---- Unsetting %d environment variables ----\n", len(variableNames))
	}
	for _, name := range variableNames {
		var unset bool
		e, err := app.getEnv(name)
		if !publicOnly || (err == nil && e.Public) {
			unset = true
		}
		if unset {
			delete(app.Env, name)
		}
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$set": bson.M{"env": app.Env}})
	if err != nil {
		return err
	}
	if !shouldRestart {
		return nil
	}
	return Provisioner.Restart(app, "", w)
}
示例#18
0
func saveDeployData(opts *DeployOptions, imageId, log string, duration time.Duration, deployError error) error {
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	deploy := DeployData{
		App:       opts.App.Name,
		Timestamp: time.Now(),
		Duration:  duration,
		Commit:    opts.Commit,
		Image:     imageId,
		Log:       log,
		User:      opts.User,
	}
	if opts.Commit != "" {
		deploy.Origin = "git"
	} else if opts.Image != "" {
		deploy.Origin = "rollback"
	} else {
		deploy.Origin = "app-deploy"
	}
	if deployError != nil {
		deploy.Error = deployError.Error()
	}
	return conn.Deploys().Insert(deploy)
}
示例#19
0
文件: app.go 项目: Zapelini/tsuru
func (app *App) RemoveCName(cnames ...string) error {
	for _, cname := range cnames {
		count := 0
		for _, appCname := range app.CName {
			if cname == appCname {
				count += 1
			}
		}
		if count == 0 {
			return stderr.New("cname not exists!")
		}
		if s, ok := Provisioner.(provision.CNameManager); ok {
			if err := s.UnsetCName(app, cname); err != nil {
				return err
			}
		}
		conn, err := db.Conn()
		if err != nil {
			return err
		}
		defer conn.Close()
		err = conn.Apps().Update(
			bson.M{"name": app.Name},
			bson.M{"$pull": bson.M{"cname": cname}},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
示例#20
0
func (p *FakeDockerProvisioner) Collection() *storage.Collection {
	conn, err := db.Conn()
	if err != nil {
		panic(err)
	}
	return conn.Collection("fake_docker_provisioner")
}
示例#21
0
文件: app.go 项目: Zapelini/tsuru
// Swap calls the Provisioner.Swap.
// And updates the app.CName in the database.
func Swap(app1, app2 *App) error {
	err := Provisioner.Swap(app1, app2)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	app1.CName, app2.CName = app2.CName, app1.CName
	updateCName := func(app *App) error {
		app.Ip, err = Provisioner.Addr(app)
		if err != nil {
			return err
		}
		return conn.Apps().Update(
			bson.M{"name": app.Name},
			bson.M{"$set": bson.M{"cname": app.CName, "ip": app.Ip}},
		)
	}
	err = updateCName(app1)
	if err != nil {
		return err
	}
	return updateCName(app2)
}
示例#22
0
文件: db.go 项目: tsuru/tsuru
func nodeAddrCollection() (*storage.Collection, error) {
	conn, err := db.Conn()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return conn.Collection(mesosCollectionName), nil
}
示例#23
0
文件: app.go 项目: Zapelini/tsuru
// Revoke removes the access from a team. It returns an error if the team do
// not have access to the app.
func (app *App) Revoke(team *auth.Team) error {
	if len(app.Teams) == 1 {
		return ErrCannotOrphanApp
	}
	index, found := app.findTeam(team)
	if !found {
		return ErrNoAccess
	}
	last := len(app.Teams) - 1
	app.Teams[index] = app.Teams[last]
	app.Teams = app.Teams[:last]
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$pull": bson.M{"teams": team.Name}})
	if err != nil {
		return err
	}
	for _, user := range app.usersToRevoke(team) {
		err = repository.Manager().RevokeAccess(app.Name, user)
		if err != nil {
			conn.Apps().Update(bson.M{"name": app.Name}, bson.M{"$addToSet": bson.M{"teams": team.Name}})
			return err
		}
	}
	return nil
}
示例#24
0
func CheckUserAccess(teamNames []string, u *User) bool {
	q := bson.M{"_id": bson.M{"$in": teamNames}}
	var teams []Team
	conn, err := db.Conn()
	if err != nil {
		log.Errorf("Failed to connect to the database: %s", err)
		return false
	}
	defer conn.Close()
	conn.Teams().Find(q).All(&teams)
	var wg sync.WaitGroup
	found := make(chan bool, len(teams)+1)
	for _, team := range teams {
		wg.Add(1)
		go func(t Team) {
			if t.ContainsUser(u) {
				found <- true
			}
			wg.Done()
		}(team)
	}
	go func() {
		wg.Wait()
		found <- false
	}()
	return <-found
}
示例#25
0
func (p *dockerProvisioner) Collection() *storage.Collection {
	conn, err := db.Conn()
	if err != nil {
		log.Errorf("Failed to connect to the database: %s", err)
	}
	return conn.Collection(p.collectionName)
}
示例#26
0
func PlatformRemove(name string) error {
	var (
		provisioner provision.ExtensibleProvisioner
		ok          bool
	)
	if provisioner, ok = Provisioner.(provision.ExtensibleProvisioner); !ok {
		return errors.New("Provisioner is not extensible")
	}
	if name == "" {
		return errors.New("Platform name is required!")
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	apps, _ := conn.Apps().Find(bson.M{"framework": name}).Count()
	if apps > 0 {
		return errors.New("Platform has apps. You should remove them before remove the platform.")
	}
	err = provisioner.PlatformRemove(name)
	if err != nil {
		log.Errorf("Failed to remove platform from provisioner: %s", err)
	}
	return conn.Platforms().Remove(bson.M{"_id": name})
}
示例#27
0
func (s *S) SetUpSuite(c *gocheck.C) {
	s.collName = "docker_unit"
	s.imageCollName = "docker_image"
	s.gitHost = "my.gandalf.com"
	s.repoNamespace = "tsuru"
	s.sshUser = "******"
	config.Set("git:ro-host", s.gitHost)
	config.Set("database:url", "127.0.0.1:27017")
	config.Set("database:name", "docker_provision_tests_s")
	config.Set("docker:repository-namespace", s.repoNamespace)
	config.Set("docker:router", "fake")
	config.Set("docker:collection", s.collName)
	config.Set("docker:deploy-cmd", "/var/lib/tsuru/deploy")
	config.Set("docker:run-cmd:bin", "/usr/local/bin/circusd /etc/circus/circus.ini")
	config.Set("docker:run-cmd:port", "8888")
	config.Set("docker:ssh:add-key-cmd", "/var/lib/tsuru/add-key")
	config.Set("docker:ssh:user", s.sshUser)
	config.Set("queue", "fake")
	s.deployCmd = "/var/lib/tsuru/deploy"
	s.runBin = "/usr/local/bin/circusd"
	s.runArgs = "/etc/circus/circus.ini"
	s.port = "8888"
	fsystem = &ftesting.RecordingFs{}
	f, err := fsystem.Create(os.ExpandEnv("${HOME}/.ssh/id_rsa.pub"))
	c.Assert(err, gocheck.IsNil)
	f.Write([]byte("key-content"))
	f.Close()
	s.server, err = dtesting.NewServer("127.0.0.1:0", nil)
	c.Assert(err, gocheck.IsNil)
	s.targetRecover = tTesting.SetTargetFile(c)
	s.storage, err = db.Conn()
	c.Assert(err, gocheck.IsNil)
}
示例#28
0
// PlatformAdd add a new platform to tsuru
func PlatformAdd(name string, args map[string]string, w io.Writer) error {
	var (
		provisioner provision.ExtensibleProvisioner
		ok          bool
	)
	if provisioner, ok = Provisioner.(provision.ExtensibleProvisioner); !ok {
		return errors.New("Provisioner is not extensible")
	}
	if name == "" {
		return errors.New("Platform name is required.")
	}
	p := Platform{Name: name}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	err = conn.Platforms().Insert(p)
	if err != nil {
		if mgo.IsDup(err) {
			return DuplicatePlatformError{}
		}
		return err
	}
	err = provisioner.PlatformAdd(name, args, w)
	if err != nil {
		dbErr := conn.Platforms().RemoveId(p.Name)
		if dbErr != nil {
			return fmt.Errorf("Caused by: %s and %s", err.Error(), dbErr.Error())
		}
		return err
	}
	return nil
}
示例#29
0
func GetServicesInstancesByTeamsAndNames(teams []string, names []string, appName, serviceName string) ([]ServiceInstance, error) {
	filter := bson.M{}
	if teams != nil || names != nil {
		filter = bson.M{
			"$or": []bson.M{
				{"teams": bson.M{"$in": teams}},
				{"name": bson.M{"$in": names}},
			},
		}
	}
	if appName != "" {
		filter["apps"] = appName
	}
	if serviceName != "" {
		filter["service_name"] = serviceName
	}
	conn, err := db.Conn()
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	var instances []ServiceInstance
	err = conn.ServiceInstances().Find(filter).All(&instances)
	return instances, err
}
示例#30
0
文件: event.go 项目: tsuru/tsuru
func (e *Event) TryCancel(reason, owner string) error {
	if !e.Cancelable || !e.Running {
		return ErrNotCancelable
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	coll := conn.Events()
	change := mgo.Change{
		Update: bson.M{"$set": bson.M{
			"cancelinfo": cancelInfo{
				Owner:     owner,
				Reason:    reason,
				StartTime: time.Now().UTC(),
				Asked:     true,
			},
		}},
		ReturnNew: true,
	}
	_, err = coll.Find(bson.M{"_id": e.ID, "cancelinfo.asked": false}).Apply(change, &e.eventData)
	if err == mgo.ErrNotFound {
		return ErrEventNotFound
	}
	return err
}