Repository: hashicorp/nomad
File: nomad/fsm.go (Go)

Code before:
			}
			if existingJob != nil {
				if req.PreserveCounts {
					prevCounts := make(map[string]int)
					for _, tg := range existingJob.TaskGroups {
						prevCounts[tg.Name] = tg.Count
					}
					for _, tg := range req.Job.TaskGroups {
						if count, ok := prevCounts[tg.Name]; ok {
							tg.Count = count
						}
					}
				}

				if req.PreserveResources {
					prevResources := make(map[string]map[string]*structs.Resources)
					for _, tg := range existingJob.TaskGroups {
						prevResources[tg.Name] = make(map[string]*structs.Resources)
						for _, task := range tg.Tasks {
							prevResources[tg.Name][task.Name] = task.Resources
						}
					}
					for _, tg := range req.Job.TaskGroups {
						for _, task := range tg.Tasks {
							if res, ok := prevResources[tg.Name][task.Name]; ok {
								task.Resources = res
							}
						}
					}
				}
			}
		}
	}

	/* Handle upgrade paths:
	 * - Empty maps and slices should be treated as nil to avoid
	 *   un-intended destructive updates in scheduler since we use
	 *   reflect.DeepEqual. Starting Nomad 0.4.1, job submission sanitizes
	 *   the incoming job.
	 * - Migrate from old style upgrade block that used only a stagger.
	 */
	req.Job.Canonicalize()

	if err := n.state.UpsertJob(msgType, index, req.Submission, req.Job); err != nil {
		n.logger.Error("UpsertJob failed", "error", err)
		return err
	}

	// We always add the job to the periodic dispatcher because there is the
	// possibility that the periodic spec was removed and then we should stop
	// tracking it.

Review comment (suggestion):
We're in the FSM so we should call `Copy()` here to make sure we're getting a new pointer to these resources. That prevents downstream callers from mutating the previous version of the job.

Code after:
	if err := n.state.UpsertJobWithRequest(msgType, index, &req); err != nil {
		n.logger.Error("UpsertJob failed", "error", err)
		return err
	}

	// We always add the job to the periodic dispatcher because there is the
	// possibility that the periodic spec was removed and then we should stop
	// tracking it.
	if err := n.periodicDispatcher.Add(req.Job); err != nil {
		n.logger.Error("periodicDispatcher.Add failed", "error", err)
		return fmt.Errorf("failed adding job to periodic dispatcher: %v", err)
	}

	// Create a watch set
	ws := memdb.NewWatchSet()

	// If it is an active periodic job, record the time it was inserted. This is
	// necessary for recovering during leader election. It is possible that from
	// the time it is added to when it was suppose to launch, leader election
	// occurs and the job was not launched. In this case, we use the insertion
	// time to determine if a launch was missed.
	if req.Job.IsPeriodicActive() {
		prevLaunch, err := n.state.PeriodicLaunchByID(ws, req.Namespace, req.Job.ID)
		if err != nil {
			n.logger.Error("PeriodicLaunchByID failed", "error", err)
			return err
		}

		// Record the insertion time as a launch. We overload the launch table
		// such that the first entry is the insertion time.
		if prevLaunch == nil {
			launch := &structs.PeriodicLaunch{
				ID:        req.Job.ID,
				Namespace: req.Namespace,
				Launch:    time.Now(),
			}
			if err := n.state.UpsertPeriodicLaunch(index, launch); err != nil {
				n.logger.Error("UpsertPeriodicLaunch failed", "error", err)
				return err
			}
		}
	}

	// Check if the parent job is periodic and mark the launch time.
	parentID := req.Job.ParentID
	if parentID != "" {
		parent, err := n.state.JobByID(ws, req.Namespace, parentID)
		if err != nil {
			n.logger.Error("JobByID lookup for parent failed", "parent_id", parentID, "namespace", req.Namespace, "error", err)
			return err
		} else if parent == nil {