func (q *Queue) CheckUsage(providerName string, m provider.Machine, bm *provider.BaseMachine, ctx context.Context) error { q.Log.Debug("Checking %q machine\n%+v\n", providerName, bm.Machine) c, err := klient.Connect(q.Kite, bm.QueryString) if err != nil { q.Log.Debug("Error connecting to klient, stopping if needed. Error: %s", err) return err } // replace with the real and authenticated username if bm.User == nil { bm.User = &models.User{} } bm.User.Name = c.Username // get the usage directly from the klient, which is the most predictable source usg, err := c.Usage() c.Close() // close the underlying connection once we get the usage if err != nil { return fmt.Errorf("failure getting %q klient usage: %s", bm.QueryString, err) } q.Log.Debug("machine [%s] (aws) is inactive for %s (plan limit: %s)", bm.IpAddress, usg.InactiveDuration, planTimeout) // It still have plenty of time to work, do not stop it if usg.InactiveDuration <= planTimeout { return nil } q.Log.Info("machine [%s] has reached current plan limit of %s. Shutting down...", bm.IpAddress, usg.InactiveDuration) // Hasta la vista, baby! q.Log.Info("[%s] ======> STOP started (closing inactive machine)<======", bm.ObjectId.Hex()) meta, err := m.Stop(ctx) if err != nil { // returning is ok, because Kloud will mark it anyways as stopped if // Klient is not rechable anymore with the `info` method q.Log.Info("[%s] ======> STOP aborted (closing inactive machine: %s)<======", bm.ObjectId.Hex(), err) return err } q.Log.Info("[%s] ======> STOP finished (closing inactive machine)<======", bm.ObjectId.Hex()) obj := object.MetaBuilder.Build(meta) obj["status.modifiedAt"] = time.Now().UTC() obj["status.state"] = machinestate.Stopped.String() obj["status.reason"] = "Machine is stopped due to inactivity" return modelhelper.UpdateMachine(bm.ObjectId, bson.M{"$set": obj}) }
// Migrate implements the Database interface. func (db *mongoDatabase) Migrate(opts *MigrateOptions) error { stack := models.NewStackTemplate(opts.Provider, opts.Identifier) stack.Machines = make([]bson.M, len(opts.Machines)) for i := range stack.Machines { stack.Machines[i] = bson.M(machineBuilder.Build(opts.Machines[i])) } account, err := modelhelper.GetAccount(opts.Username) if err != nil { return fmt.Errorf("account lookup failed for %q: %s", opts.Username, err) } sum := sha1.Sum([]byte(opts.Template)) stack.Title = opts.StackName stack.OriginID = account.Id stack.Template.Details = bson.M{ "lastUpdaterId": account.Id, } stack.Group = opts.GroupName stack.Template.Content = opts.Template stack.Template.Sum = hex.EncodeToString(sum[:]) if s, err := yamlReencode(opts.Template); err == nil { stack.Template.RawContent = s } if err := modelhelper.CreateStackTemplate(stack); err != nil { return fmt.Errorf("failed to create stack template: %s", err) } change := bson.M{ "$set": bson.M{ "meta.migration.modifiedAt": time.Now(), "meta.migration.status": MigrationMigrated, "meta.migration.stackTemplateId": stack.Id, }, } for _, id := range opts.MachineIDs { if e := modelhelper.UpdateMachine(id, change); e != nil { err = multierror.Append(err, fmt.Errorf("failed to update migration details for %q: %s", id.Hex(), err)) } } // Failure updating jMachine migration metadata is not critical, // just log the error and continue. if err != nil { opts.Log.Error("%s", err) } return nil }
func (cmd *GroupFixDomain) update(records []*dnsclient.Record, ids []bson.ObjectId) error { merr := new(multierror.Error) for i, rec := range records { err := modelhelper.UpdateMachine(ids[i], bson.M{"domain": rec.Name}) if err != nil { err = fmt.Errorf("failed updating %q domain to %q: %s", ids[i].Hex(), rec.Name, err) merr = multierror.Append(merr, err) } } return merr.ErrorOrNil() }
func (bs *BaseStack) UpdateResources(state *terraform.State) error { machines, err := bs.state(state, bs.Klients) if err != nil { return err } now := time.Now().UTC() for label, m := range bs.Builder.Machines { machine, ok := machines[label] if !ok { err = multierror.Append(err, fmt.Errorf("machine %q does not exist in terraform state file", label)) continue } if machine.Provider != bs.Planner.Provider { continue } if cred, err := bs.Builder.CredentialByProvider(machine.Provider); err == nil { machine.Credential = cred } else { err = multierror.Append(err, fmt.Errorf("machine %q: no credential found for %q provider", label, machine.Provider)) machine.Credential = &stack.Credential{} } state, ok := bs.Klients[label] if !ok { err = multierror.Append(err, fmt.Errorf("machine %q does not exist in dial state", label)) continue } err := modelhelper.UpdateMachine(m.ObjectId, bson.M{"$set": bs.buildUpdateObj(machine, state, now)}) if err != nil { err = multierror.Append(err, fmt.Errorf("machine %q failed to update: %s", label, err)) continue } } return err }
func (bm *BaseMachine) updateMachine(state *DialState, meta interface{}, dbState machinestate.State) error { obj := object.MetaBuilder.Build(meta) if state != nil && state.KiteURL != "" { if bm.RegisterURL != state.KiteURL { obj["registerUrl"] = state.KiteURL } if u, err := url.Parse(state.KiteURL); err == nil && u.Host != "" { if host, _, err := net.SplitHostPort(u.Host); err == nil { u.Host = host } if bm.IpAddress != u.Host { // TODO(rjeczalik): when path routing is added (#9021) either we // change the ipAddress field to more generic endpoint field, // or we use here state.KiteURL directly. obj["ipAddress"] = u.Host } } } if dbState != 0 { obj["status.modifiedAt"] = time.Now().UTC() obj["status.state"] = dbState.String() obj["status.reason"] = "Machine is " + dbState.String() } if len(obj) == 0 { return nil } bm.Log.Debug("update object for %q: %+v (%# v)", bm.Label, obj, state) return modelhelper.UpdateMachine(bm.ObjectId, bson.M{"$set": obj}) }
func TestLookupGroup(t *testing.T) { const N = 10 db := modeltesthelper.NewMongoDB(t) defer db.Close() user := &models.User{ ObjectId: bson.NewObjectId(), Name: bson.NewObjectId().Hex(), Email: bson.NewObjectId().Hex(), } if err := modelhelper.CreateUser(user); err != nil { t.Fatalf("CreateUser()=%s", err) } groups, err := createGroups(N + 1) if err != nil { t.Fatalf("createGroups()=%s", err) } machines, err := createMachines(N, t) if err != nil { t.Fatalf("createMachines()=%s", err) } for i := range machines { machines[i].Groups = []models.MachineGroup{{Id: groups[i].Id}} update := bson.M{ "$set": bson.M{ "groups": machines[i].Groups, "users": []*models.MachineUser{{ Id: user.ObjectId, Username: user.Name, }}, }, } if i&2 == 0 { // force to lookup by registerUrl for machines with even index update["$set"].(bson.M)["ipAddress"] = "" } err := modelhelper.UpdateMachine(machines[i].ObjectId, update) if err != nil { t.Fatalf("UpdateMachine()=%s", err) } } session := &models.Session{ Id: bson.NewObjectId(), GroupName: groups[N].Slug, Username: user.Name, } if err := modelhelper.CreateSession(session); err != nil { t.Fatalf("CreateSession()=%s") } cases := map[string]struct { opts *modelhelper.LookupGroupOptions id bson.ObjectId }{ "lookup by queryString": { &modelhelper.LookupGroupOptions{ Username: user.Name, KiteID: mustKiteID(machines[0].QueryString), }, groups[0].Id, }, "lookup by ipAddress": { &modelhelper.LookupGroupOptions{ Username: user.Name, ClientURL: machines[1].RegisterURL, }, groups[1].Id, }, "lookup by registerUrl": { &modelhelper.LookupGroupOptions{ Username: user.Name, ClientURL: machines[4].RegisterURL, }, groups[4].Id, }, "lookup by most recent session for KD": { &modelhelper.LookupGroupOptions{ Username: user.Name, Environment: "managed", }, groups[N].Id, }, } for name, cas := range cases { t.Run(name, func(t *testing.T) { team, err := modelhelper.LookupGroup(cas.opts) if err != nil { t.Fatalf("LookupGroup()=%s", err) } if team.Id != cas.id { t.Fatalf("got %q, want %q", team.Id.Hex(), cas.id.Hex()) } }) } }
// UpdateMigration implements the Database interface. func (db *mongoDatabase) UpdateMigration(opts *UpdateMigrationOptions) error { change := bson.M{ "$set": migrationBuilder.Build(opts.Meta), } return modelhelper.UpdateMachine(opts.MachineID, change) }