github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/api/jobs.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"sort"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/gorhill/cronexpr"
    11  	"github.com/hashicorp/nomad/helper"
    12  )
    13  
    14  const (
    15  	// JobTypeService indicates a long-running processes
    16  	JobTypeService = "service"
    17  
    18  	// JobTypeBatch indicates a short-lived process
    19  	JobTypeBatch = "batch"
    20  
    21  	// PeriodicSpecCron is used for a cron spec.
    22  	PeriodicSpecCron = "cron"
    23  )
    24  
    25  const (
    26  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    27  	// enforcing the job modify index during registers.
    28  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    29  )
    30  
    31  // Jobs is used to access the job-specific endpoints.
    32  type Jobs struct {
    33  	client *Client
    34  }
    35  
    36  // Jobs returns a handle on the jobs endpoints.
    37  func (c *Client) Jobs() *Jobs {
    38  	return &Jobs{client: c}
    39  }
    40  
    41  func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
    42  	var resp JobValidateResponse
    43  	req := &JobValidateRequest{Job: job}
    44  	if q != nil {
    45  		req.WriteRequest = WriteRequest{Region: q.Region}
    46  	}
    47  	wm, err := j.client.write("/v1/validate/job", req, &resp, q)
    48  	return &resp, wm, err
    49  }
    50  
    51  // Register is used to register a new job. It returns the ID
    52  // of the evaluation, along with any errors encountered.
    53  func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) {
    54  
    55  	var resp JobRegisterResponse
    56  
    57  	req := &RegisterJobRequest{Job: job}
    58  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
    59  	if err != nil {
    60  		return "", nil, err
    61  	}
    62  	return resp.EvalID, wm, nil
    63  }
    64  
    65  // EnforceRegister is used to register a job enforcing its job modify index.
    66  func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (string, *WriteMeta, error) {
    67  
    68  	var resp JobRegisterResponse
    69  
    70  	req := &RegisterJobRequest{
    71  		Job:            job,
    72  		EnforceIndex:   true,
    73  		JobModifyIndex: modifyIndex,
    74  	}
    75  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
    76  	if err != nil {
    77  		return "", nil, err
    78  	}
    79  	return resp.EvalID, wm, nil
    80  }
    81  
    82  // List is used to list all of the existing jobs.
    83  func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
    84  	var resp []*JobListStub
    85  	qm, err := j.client.query("/v1/jobs", &resp, q)
    86  	if err != nil {
    87  		return nil, qm, err
    88  	}
    89  	sort.Sort(JobIDSort(resp))
    90  	return resp, qm, nil
    91  }
    92  
    93  // PrefixList is used to list all existing jobs that match the prefix.
    94  func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
    95  	return j.List(&QueryOptions{Prefix: prefix})
    96  }
    97  
    98  // Info is used to retrieve information about a particular
    99  // job given its unique ID.
   100  func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
   101  	var resp Job
   102  	qm, err := j.client.query("/v1/job/"+jobID, &resp, q)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  	return &resp, qm, nil
   107  }
   108  
   109  // Versions is used to retrieve all versions of a particular
   110  // job given its unique ID.
   111  func (j *Jobs) Versions(jobID string, q *QueryOptions) ([]*Job, *QueryMeta, error) {
   112  	var resp []*Job
   113  	qm, err := j.client.query("/v1/job/"+jobID+"/versions", &resp, q)
   114  	if err != nil {
   115  		return nil, nil, err
   116  	}
   117  	return resp, qm, nil
   118  }
   119  
   120  // Allocations is used to return the allocs for a given job ID.
   121  func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
   122  	var resp []*AllocationListStub
   123  	u, err := url.Parse("/v1/job/" + jobID + "/allocations")
   124  	if err != nil {
   125  		return nil, nil, err
   126  	}
   127  
   128  	v := u.Query()
   129  	v.Add("all", strconv.FormatBool(allAllocs))
   130  	u.RawQuery = v.Encode()
   131  
   132  	qm, err := j.client.query(u.String(), &resp, q)
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  	sort.Sort(AllocIndexSort(resp))
   137  	return resp, qm, nil
   138  }
   139  
   140  // Evaluations is used to query the evaluations associated with
   141  // the given job ID.
   142  func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
   143  	var resp []*Evaluation
   144  	qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q)
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  	sort.Sort(EvalIndexSort(resp))
   149  	return resp, qm, nil
   150  }
   151  
   152  // Deregister is used to remove an existing job. If purge is set to true, the job
   153  // is deregistered and purged from the system versus still being queryable and
   154  // eventually GC'ed from the system. Most callers should not specify purge.
   155  func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) {
   156  	var resp JobDeregisterResponse
   157  	wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", jobID, purge), &resp, q)
   158  	if err != nil {
   159  		return "", nil, err
   160  	}
   161  	return resp.EvalID, wm, nil
   162  }
   163  
   164  // ForceEvaluate is used to force-evaluate an existing job.
   165  func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   166  	var resp JobRegisterResponse
   167  	wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q)
   168  	if err != nil {
   169  		return "", nil, err
   170  	}
   171  	return resp.EvalID, wm, nil
   172  }
   173  
   174  // PeriodicForce spawns a new instance of the periodic job and returns the eval ID
   175  func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   176  	var resp periodicForceResponse
   177  	wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q)
   178  	if err != nil {
   179  		return "", nil, err
   180  	}
   181  	return resp.EvalID, wm, nil
   182  }
   183  
   184  func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   185  	if job == nil {
   186  		return nil, nil, fmt.Errorf("must pass non-nil job")
   187  	}
   188  
   189  	var resp JobPlanResponse
   190  	req := &JobPlanRequest{
   191  		Job:  job,
   192  		Diff: diff,
   193  	}
   194  	wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q)
   195  	if err != nil {
   196  		return nil, nil, err
   197  	}
   198  
   199  	return &resp, wm, nil
   200  }
   201  
   202  func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
   203  	var resp JobSummary
   204  	qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q)
   205  	if err != nil {
   206  		return nil, nil, err
   207  	}
   208  	return &resp, qm, nil
   209  }
   210  
   211  func (j *Jobs) Dispatch(jobID string, meta map[string]string,
   212  	payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
   213  	var resp JobDispatchResponse
   214  	req := &JobDispatchRequest{
   215  		JobID:   jobID,
   216  		Meta:    meta,
   217  		Payload: payload,
   218  	}
   219  	wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q)
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  	return &resp, wm, nil
   224  }
   225  
   226  // Revert is used to revert the given job to the passed version. If
   227  // enforceVersion is set, the job is only reverted if the current version is at
   228  // the passed version.
   229  func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64,
   230  	q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   231  
   232  	var resp JobRegisterResponse
   233  	req := &JobRevertRequest{
   234  		JobID:               jobID,
   235  		JobVersion:          version,
   236  		EnforcePriorVersion: enforcePriorVersion,
   237  	}
   238  	wm, err := j.client.write("/v1/job/"+jobID+"/revert", req, &resp, q)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  	return &resp, wm, nil
   243  }
   244  
   245  // periodicForceResponse is used to deserialize a force response
   246  type periodicForceResponse struct {
   247  	EvalID string
   248  }
   249  
   250  // UpdateStrategy is for serializing update strategy for a job.
   251  type UpdateStrategy struct {
   252  	Stagger     time.Duration
   253  	MaxParallel int `mapstructure:"max_parallel"`
   254  }
   255  
   256  // PeriodicConfig is for serializing periodic config for a job.
   257  type PeriodicConfig struct {
   258  	Enabled         *bool
   259  	Spec            *string
   260  	SpecType        *string
   261  	ProhibitOverlap *bool   `mapstructure:"prohibit_overlap"`
   262  	TimeZone        *string `mapstructure:"time_zone"`
   263  }
   264  
   265  func (p *PeriodicConfig) Canonicalize() {
   266  	if p.Enabled == nil {
   267  		p.Enabled = helper.BoolToPtr(true)
   268  	}
   269  	if p.Spec == nil {
   270  		p.Spec = helper.StringToPtr("")
   271  	}
   272  	if p.SpecType == nil {
   273  		p.SpecType = helper.StringToPtr(PeriodicSpecCron)
   274  	}
   275  	if p.ProhibitOverlap == nil {
   276  		p.ProhibitOverlap = helper.BoolToPtr(false)
   277  	}
   278  	if p.TimeZone == nil || *p.TimeZone == "" {
   279  		p.TimeZone = helper.StringToPtr("UTC")
   280  	}
   281  }
   282  
   283  // Next returns the closest time instant matching the spec that is after the
   284  // passed time. If no matching instance exists, the zero value of time.Time is
   285  // returned. The `time.Location` of the returned value matches that of the
   286  // passed time.
   287  func (p *PeriodicConfig) Next(fromTime time.Time) time.Time {
   288  	if *p.SpecType == PeriodicSpecCron {
   289  		if e, err := cronexpr.Parse(*p.Spec); err == nil {
   290  			return e.Next(fromTime)
   291  		}
   292  	}
   293  
   294  	return time.Time{}
   295  }
   296  
   297  func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
   298  	if p.TimeZone == nil || *p.TimeZone == "" {
   299  		return time.UTC, nil
   300  	}
   301  
   302  	return time.LoadLocation(*p.TimeZone)
   303  }
   304  
   305  // ParameterizedJobConfig is used to configure the parameterized job.
   306  type ParameterizedJobConfig struct {
   307  	Payload      string
   308  	MetaRequired []string `mapstructure:"meta_required"`
   309  	MetaOptional []string `mapstructure:"meta_optional"`
   310  }
   311  
   312  // Job is used to serialize a job.
   313  type Job struct {
   314  	Stop              *bool
   315  	Region            *string
   316  	ID                *string
   317  	ParentID          *string
   318  	Name              *string
   319  	Type              *string
   320  	Priority          *int
   321  	AllAtOnce         *bool `mapstructure:"all_at_once"`
   322  	Datacenters       []string
   323  	Constraints       []*Constraint
   324  	TaskGroups        []*TaskGroup
   325  	Update            *UpdateStrategy
   326  	Periodic          *PeriodicConfig
   327  	ParameterizedJob  *ParameterizedJobConfig
   328  	Payload           []byte
   329  	Meta              map[string]string
   330  	VaultToken        *string `mapstructure:"vault_token"`
   331  	Status            *string
   332  	StatusDescription *string
   333  	Stable            *bool
   334  	Version           *uint64
   335  	CreateIndex       *uint64
   336  	ModifyIndex       *uint64
   337  	JobModifyIndex    *uint64
   338  }
   339  
   340  // IsPeriodic returns whether a job is periodic.
   341  func (j *Job) IsPeriodic() bool {
   342  	return j.Periodic != nil
   343  }
   344  
   345  // IsParameterized returns whether a job is parameterized job.
   346  func (j *Job) IsParameterized() bool {
   347  	return j.ParameterizedJob != nil
   348  }
   349  
   350  func (j *Job) Canonicalize() {
   351  	if j.ID == nil {
   352  		j.ID = helper.StringToPtr("")
   353  	}
   354  	if j.Name == nil {
   355  		j.Name = helper.StringToPtr(*j.ID)
   356  	}
   357  	if j.ParentID == nil {
   358  		j.ParentID = helper.StringToPtr("")
   359  	}
   360  	if j.Priority == nil {
   361  		j.Priority = helper.IntToPtr(50)
   362  	}
   363  	if j.Stop == nil {
   364  		j.Stop = helper.BoolToPtr(false)
   365  	}
   366  	if j.Region == nil {
   367  		j.Region = helper.StringToPtr("global")
   368  	}
   369  	if j.Type == nil {
   370  		j.Type = helper.StringToPtr("service")
   371  	}
   372  	if j.AllAtOnce == nil {
   373  		j.AllAtOnce = helper.BoolToPtr(false)
   374  	}
   375  	if j.VaultToken == nil {
   376  		j.VaultToken = helper.StringToPtr("")
   377  	}
   378  	if j.Status == nil {
   379  		j.Status = helper.StringToPtr("")
   380  	}
   381  	if j.StatusDescription == nil {
   382  		j.StatusDescription = helper.StringToPtr("")
   383  	}
   384  	if j.Stable == nil {
   385  		j.Stable = helper.BoolToPtr(false)
   386  	}
   387  	if j.Version == nil {
   388  		j.Version = helper.Uint64ToPtr(0)
   389  	}
   390  	if j.CreateIndex == nil {
   391  		j.CreateIndex = helper.Uint64ToPtr(0)
   392  	}
   393  	if j.ModifyIndex == nil {
   394  		j.ModifyIndex = helper.Uint64ToPtr(0)
   395  	}
   396  	if j.JobModifyIndex == nil {
   397  		j.JobModifyIndex = helper.Uint64ToPtr(0)
   398  	}
   399  	if j.Periodic != nil {
   400  		j.Periodic.Canonicalize()
   401  	}
   402  
   403  	for _, tg := range j.TaskGroups {
   404  		tg.Canonicalize(j)
   405  	}
   406  }
   407  
   408  // JobSummary summarizes the state of the allocations of a job
   409  type JobSummary struct {
   410  	JobID    string
   411  	Summary  map[string]TaskGroupSummary
   412  	Children *JobChildrenSummary
   413  
   414  	// Raft Indexes
   415  	CreateIndex uint64
   416  	ModifyIndex uint64
   417  }
   418  
   419  // JobChildrenSummary contains the summary of children job status
   420  type JobChildrenSummary struct {
   421  	Pending int64
   422  	Running int64
   423  	Dead    int64
   424  }
   425  
   426  func (jc *JobChildrenSummary) Sum() int {
   427  	if jc == nil {
   428  		return 0
   429  	}
   430  
   431  	return int(jc.Pending + jc.Running + jc.Dead)
   432  }
   433  
   434  // TaskGroup summarizes the state of all the allocations of a particular
   435  // TaskGroup
   436  type TaskGroupSummary struct {
   437  	Queued   int
   438  	Complete int
   439  	Failed   int
   440  	Running  int
   441  	Starting int
   442  	Lost     int
   443  }
   444  
   445  // JobListStub is used to return a subset of information about
   446  // jobs during list operations.
   447  type JobListStub struct {
   448  	ID                string
   449  	ParentID          string
   450  	Name              string
   451  	Type              string
   452  	Priority          int
   453  	Periodic          bool
   454  	ParameterizedJob  bool
   455  	Stop              bool
   456  	Status            string
   457  	StatusDescription string
   458  	JobSummary        *JobSummary
   459  	CreateIndex       uint64
   460  	ModifyIndex       uint64
   461  	JobModifyIndex    uint64
   462  }
   463  
   464  // JobIDSort is used to sort jobs by their job ID's.
   465  type JobIDSort []*JobListStub
   466  
   467  func (j JobIDSort) Len() int {
   468  	return len(j)
   469  }
   470  
   471  func (j JobIDSort) Less(a, b int) bool {
   472  	return j[a].ID < j[b].ID
   473  }
   474  
   475  func (j JobIDSort) Swap(a, b int) {
   476  	j[a], j[b] = j[b], j[a]
   477  }
   478  
   479  // NewServiceJob creates and returns a new service-style job
   480  // for long-lived processes using the provided name, ID, and
   481  // relative job priority.
   482  func NewServiceJob(id, name, region string, pri int) *Job {
   483  	return newJob(id, name, region, JobTypeService, pri)
   484  }
   485  
   486  // NewBatchJob creates and returns a new batch-style job for
   487  // short-lived processes using the provided name and ID along
   488  // with the relative job priority.
   489  func NewBatchJob(id, name, region string, pri int) *Job {
   490  	return newJob(id, name, region, JobTypeBatch, pri)
   491  }
   492  
   493  // newJob is used to create a new Job struct.
   494  func newJob(id, name, region, typ string, pri int) *Job {
   495  	return &Job{
   496  		Region:   &region,
   497  		ID:       &id,
   498  		Name:     &name,
   499  		Type:     &typ,
   500  		Priority: &pri,
   501  	}
   502  }
   503  
   504  // SetMeta is used to set arbitrary k/v pairs of metadata on a job.
   505  func (j *Job) SetMeta(key, val string) *Job {
   506  	if j.Meta == nil {
   507  		j.Meta = make(map[string]string)
   508  	}
   509  	j.Meta[key] = val
   510  	return j
   511  }
   512  
   513  // AddDatacenter is used to add a datacenter to a job.
   514  func (j *Job) AddDatacenter(dc string) *Job {
   515  	j.Datacenters = append(j.Datacenters, dc)
   516  	return j
   517  }
   518  
   519  // Constrain is used to add a constraint to a job.
   520  func (j *Job) Constrain(c *Constraint) *Job {
   521  	j.Constraints = append(j.Constraints, c)
   522  	return j
   523  }
   524  
   525  // AddTaskGroup adds a task group to an existing job.
   526  func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
   527  	j.TaskGroups = append(j.TaskGroups, grp)
   528  	return j
   529  }
   530  
   531  // AddPeriodicConfig adds a periodic config to an existing job.
   532  func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
   533  	j.Periodic = cfg
   534  	return j
   535  }
   536  
   537  type WriteRequest struct {
   538  	// The target region for this write
   539  	Region string
   540  }
   541  
   542  // JobValidateRequest is used to validate a job
   543  type JobValidateRequest struct {
   544  	Job *Job
   545  	WriteRequest
   546  }
   547  
   548  // JobValidateResponse is the response from validate request
   549  type JobValidateResponse struct {
   550  	// DriverConfigValidated indicates whether the agent validated the driver
   551  	// config
   552  	DriverConfigValidated bool
   553  
   554  	// ValidationErrors is a list of validation errors
   555  	ValidationErrors []string
   556  
   557  	// Error is a string version of any error that may have occured
   558  	Error string
   559  }
   560  
   561  // JobRevertRequest is used to revert a job to a prior version.
   562  type JobRevertRequest struct {
   563  	// JobID is the ID of the job  being reverted
   564  	JobID string
   565  
   566  	// JobVersion the version to revert to.
   567  	JobVersion uint64
   568  
   569  	// EnforcePriorVersion if set will enforce that the job is at the given
   570  	// version before reverting.
   571  	EnforcePriorVersion *uint64
   572  
   573  	WriteRequest
   574  }
   575  
   576  // JobUpdateRequest is used to update a job
   577  type JobRegisterRequest struct {
   578  	Job *Job
   579  	// If EnforceIndex is set then the job will only be registered if the passed
   580  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
   581  	// register only occurs if the job is new.
   582  	EnforceIndex   bool
   583  	JobModifyIndex uint64
   584  
   585  	WriteRequest
   586  }
   587  
   588  // RegisterJobRequest is used to serialize a job registration
   589  type RegisterJobRequest struct {
   590  	Job            *Job
   591  	EnforceIndex   bool   `json:",omitempty"`
   592  	JobModifyIndex uint64 `json:",omitempty"`
   593  }
   594  
   595  // JobRegisterResponse is used to respond to a job registration
   596  type JobRegisterResponse struct {
   597  	EvalID          string
   598  	EvalCreateIndex uint64
   599  	JobModifyIndex  uint64
   600  	QueryMeta
   601  }
   602  
   603  // JobDeregisterResponse is used to respond to a job deregistration
   604  type JobDeregisterResponse struct {
   605  	EvalID          string
   606  	EvalCreateIndex uint64
   607  	JobModifyIndex  uint64
   608  	QueryMeta
   609  }
   610  
   611  type JobPlanRequest struct {
   612  	Job  *Job
   613  	Diff bool
   614  	WriteRequest
   615  }
   616  
   617  type JobPlanResponse struct {
   618  	JobModifyIndex     uint64
   619  	CreatedEvals       []*Evaluation
   620  	Diff               *JobDiff
   621  	Annotations        *PlanAnnotations
   622  	FailedTGAllocs     map[string]*AllocationMetric
   623  	NextPeriodicLaunch time.Time
   624  }
   625  
   626  type JobDiff struct {
   627  	Type       string
   628  	ID         string
   629  	Fields     []*FieldDiff
   630  	Objects    []*ObjectDiff
   631  	TaskGroups []*TaskGroupDiff
   632  }
   633  
   634  type TaskGroupDiff struct {
   635  	Type    string
   636  	Name    string
   637  	Fields  []*FieldDiff
   638  	Objects []*ObjectDiff
   639  	Tasks   []*TaskDiff
   640  	Updates map[string]uint64
   641  }
   642  
   643  type TaskDiff struct {
   644  	Type        string
   645  	Name        string
   646  	Fields      []*FieldDiff
   647  	Objects     []*ObjectDiff
   648  	Annotations []string
   649  }
   650  
   651  type FieldDiff struct {
   652  	Type        string
   653  	Name        string
   654  	Old, New    string
   655  	Annotations []string
   656  }
   657  
   658  type ObjectDiff struct {
   659  	Type    string
   660  	Name    string
   661  	Fields  []*FieldDiff
   662  	Objects []*ObjectDiff
   663  }
   664  
   665  type PlanAnnotations struct {
   666  	DesiredTGUpdates map[string]*DesiredUpdates
   667  }
   668  
   669  type DesiredUpdates struct {
   670  	Ignore            uint64
   671  	Place             uint64
   672  	Migrate           uint64
   673  	Stop              uint64
   674  	InPlaceUpdate     uint64
   675  	DestructiveUpdate uint64
   676  }
   677  
   678  type JobDispatchRequest struct {
   679  	JobID   string
   680  	Payload []byte
   681  	Meta    map[string]string
   682  }
   683  
   684  type JobDispatchResponse struct {
   685  	DispatchedJobID string
   686  	EvalID          string
   687  	EvalCreateIndex uint64
   688  	JobCreateIndex  uint64
   689  	WriteMeta
   690  }