github.com/unirita/cuto@v0.9.8-0.20160830082821-aa6652f877b7/master/jobnet/network.go (about)

     1  // Copyright 2015 unirita Inc.
     2  // Created 2015/04/10 honda
     3  
     4  package jobnet
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"sync"
    13  
    14  	"github.com/unirita/cuto/console"
    15  	"github.com/unirita/cuto/db"
    16  	"github.com/unirita/cuto/db/tx"
    17  	"github.com/unirita/cuto/log"
    18  	"github.com/unirita/cuto/master/config"
    19  	"github.com/unirita/cuto/master/jobnet/parser"
    20  	"github.com/unirita/cuto/message"
    21  	"github.com/unirita/cuto/util"
    22  )
    23  
    24  // ジョブネット全体を表す構造体
    25  type Network struct {
    26  	ID         int                // ジョブネットワークID。
    27  	Name       string             // ジョブネットワーク名。
    28  	Start      Element            // スタートイベントのノード。
    29  	End        Element            // エンドイベントのノード。
    30  	MasterPath string             // ジョブネットワークファイルパス。
    31  	JobExPath  string             // 拡張ジョブ定義ファイルパス。
    32  	elements   map[string]Element // ジョブネットワークの構成要素Map。
    33  	Result     *tx.ResultMap      // 実行結果情報。
    34  	globalLock *util.LockHandle   // マスタ間ロックハンドル
    35  	localMutex sync.Mutex         // ゴルーチン間のミューテックス
    36  }
    37  
    38  // cuto masterが使用するミューテックス名。
    39  const lock_name string = "Unirita_CutoMaster.lock"
    40  
    41  // Network構造体のコンストラクタ関数
    42  //
    43  // param : name ジョブネットワーク名。
    44  //
    45  // return : ジョブネットワーク構造体。
    46  func NewNetwork(name string) (*Network, error) {
    47  	nwk := new(Network)
    48  	nwk.Name = name
    49  	nwk.elements = make(map[string]Element)
    50  	filePrefix := filepath.Join(config.Dir.JobnetDir, name)
    51  	nwk.MasterPath = filePrefix + ".bpmn"
    52  	nwk.JobExPath = filePrefix + ".csv"
    53  
    54  	var err error
    55  	nwk.globalLock, err = util.InitLock(lock_name)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	return nwk, err
    60  }
    61  
    62  // ネットワーク名nameを元にネットワーク定義ファイルをロードし、Network構造体のオブジェクトを返す。
    63  //
    64  // param : name ジョブネットワーク名。
    65  //
    66  // return : ジョブネットワーク構造体。
    67  func LoadNetwork(name string) *Network {
    68  	nwk, err := NewNetwork(name)
    69  	if err != nil {
    70  		console.Display("CTM019E", err)
    71  		return nil
    72  	}
    73  
    74  	file, err := os.Open(nwk.MasterPath)
    75  	if err != nil {
    76  		console.Display("CTM010E", nwk.MasterPath)
    77  		log.Error(err)
    78  		return nil
    79  	}
    80  	defer file.Close()
    81  
    82  	err = nwk.LoadElements(file)
    83  	if err != nil {
    84  		console.Display("CTM011E", nwk.MasterPath, err)
    85  		return nil
    86  	}
    87  
    88  	return nwk
    89  }
    90  
    91  // io.Readerからネットワーク定義を読み込み、n.Start/n.End/n.elementsへ値をセットする。
    92  //
    93  // param : r Reader。
    94  //
    95  // return : エラー情報。
    96  func (n *Network) LoadElements(r io.Reader) error {
    97  	proc, err := parser.ParseNetwork(r)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	return n.setElements(proc)
   103  }
   104  
   105  // BPMNパース結果のProcess構造体からネットワークの各要素を取得し、セットする。
   106  func (n *Network) setElements(proc *parser.Process) error {
   107  	for _, t := range proc.Task {
   108  		if _, exists := n.elements[t.ID]; exists {
   109  			return fmt.Errorf("Element[id = %s] duplicated.", t.ID)
   110  		}
   111  
   112  		var err error
   113  		n.elements[t.ID], err = NewJob(t.ID, t.Name, n)
   114  		if err != nil {
   115  			return err
   116  		}
   117  	}
   118  
   119  	for _, g := range proc.Gateway {
   120  		if _, exists := n.elements[g.ID]; exists {
   121  			return fmt.Errorf("Element[id = %s] duplicated.", g.ID)
   122  		}
   123  		n.elements[g.ID] = NewGateway(g.ID)
   124  	}
   125  
   126  	sid := proc.Start[0].ID
   127  	eid := proc.End[0].ID
   128  
   129  	for _, f := range proc.Flow {
   130  		switch {
   131  		case f.From == sid:
   132  			if n.Start != nil {
   133  				return fmt.Errorf("StartEvent cannot connect with over 1 element.")
   134  			}
   135  
   136  			if f.To == eid {
   137  				return fmt.Errorf("Jobnet is empty.")
   138  			}
   139  
   140  			var ok bool
   141  			n.Start, ok = n.elements[f.To]
   142  			if !ok {
   143  				return fmt.Errorf("StartEvent connects with imaginary element[id = %s].", f.To)
   144  			}
   145  		case f.To == eid:
   146  			if n.End != nil {
   147  				return fmt.Errorf("EndEvent cannot connect with over 1 element.")
   148  			}
   149  
   150  			var ok bool
   151  			n.End, ok = n.elements[f.From]
   152  			if !ok {
   153  				return fmt.Errorf("EndEvent connects with imaginary element[id = %s].", f.From)
   154  			}
   155  		default:
   156  			from, ok := n.elements[f.From]
   157  			if !ok {
   158  				return fmt.Errorf("There is a sequenceFlow which refers imaginary element[id = %s].", f.From)
   159  			}
   160  			to, ok := n.elements[f.To]
   161  			if !ok {
   162  				return fmt.Errorf("There is a sequenceFlow which refers imaginary element[id = %s].", f.To)
   163  			}
   164  			if err := from.AddNext(to); err != nil {
   165  				return err
   166  			}
   167  		}
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  // JobExファイルをロードし、ネットワーク内のジョブへ拡張ジョブ定義をセットする。
   174  //
   175  // return : エラー情報。
   176  func (n *Network) LoadJobEx() error {
   177  	jobEx, err := parser.ParseJobExFile(n.JobExPath)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	n.setJobEx(jobEx)
   182  
   183  	return nil
   184  }
   185  
   186  // ネットワーク内のジョブへ拡張ジョブ定義のパース結果をセットする。
   187  func (n *Network) setJobEx(m map[string]*parser.JobEx) {
   188  	for _, e := range n.elements {
   189  		switch e.(type) {
   190  		case *Job:
   191  			j := e.(*Job)
   192  			if je, ok := m[j.Name]; ok {
   193  				j.Node = je.Node
   194  				j.Port = je.Port
   195  				j.FilePath = je.FilePath
   196  				j.Param = je.Param
   197  				j.Env = je.Env
   198  				j.Workspace = je.Workspace
   199  				j.WrnRC = je.WrnRC
   200  				j.WrnPtn = je.WrnPtn
   201  				j.ErrRC = je.ErrRC
   202  				j.ErrPtn = je.ErrPtn
   203  				j.Timeout = je.TimeoutMin * 60
   204  				j.SecondaryNode = je.SecondaryNode
   205  				j.SecondaryPort = je.SecondaryPort
   206  			}
   207  			j.SetDefaultEx()
   208  		default:
   209  			continue
   210  		}
   211  	}
   212  }
   213  
   214  // 実行フローのエラー検出を行う。
   215  //
   216  // return : エラー情報。
   217  func (n *Network) DetectFlowError() error {
   218  	if n.Start == nil {
   219  		return fmt.Errorf("There is no element which connects with startEvent.")
   220  	}
   221  	if n.End == nil {
   222  		return fmt.Errorf("There is no element which connects with endEvent.")
   223  	}
   224  
   225  	novisit := make(map[string]Element)
   226  	for k, v := range n.elements {
   227  		novisit[k] = v
   228  	}
   229  
   230  	err := n.scanFlow(n.Start, novisit)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	if len(novisit) > 0 {
   236  		return fmt.Errorf("Isolated element is detected.")
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  func (n *Network) scanFlow(e Element, novisit map[string]Element) error {
   243  	delete(novisit, e.ID())
   244  	if e == n.End {
   245  		if e.HasNext() {
   246  			return fmt.Errorf("Element which connects with endEvent cannot connect with another element.")
   247  		}
   248  		return nil
   249  	} else if !e.HasNext() {
   250  		return fmt.Errorf("Element[id = %s] cannot terminate network because it is not a endEvent.", e.ID)
   251  	}
   252  
   253  	switch e.(type) {
   254  	case *Job:
   255  		j := e.(*Job)
   256  		return n.scanFlow(j.Next, novisit)
   257  	case *Gateway:
   258  		g := e.(*Gateway)
   259  
   260  		if len(g.Nexts) == 1 {
   261  			return n.scanFlow(g.Nexts[0], novisit)
   262  		} else {
   263  			var jct Element = nil
   264  			for _, branch := range g.Nexts {
   265  				bind, err := n.scanFlowParallel(branch, novisit)
   266  				if err != nil {
   267  					return err
   268  				}
   269  
   270  				if jct == nil {
   271  					jct = bind
   272  				} else if jct != bind {
   273  					return fmt.Errorf("Cannot nest branches.")
   274  				}
   275  			}
   276  
   277  			return n.scanFlow(jct, novisit)
   278  		}
   279  	default:
   280  		return fmt.Errorf("Irregal element was detected.")
   281  	}
   282  }
   283  
   284  func (n *Network) scanFlowParallel(e Element, novisit map[string]Element) (Element, error) {
   285  	delete(novisit, e.ID())
   286  	switch e.(type) {
   287  	case *Job:
   288  		j := e.(*Job)
   289  		if j.Next == nil {
   290  			return nil, fmt.Errorf("EndEvent cannot connect with branch.")
   291  		}
   292  		return n.scanFlowParallel(j.Next, novisit)
   293  	case *Gateway:
   294  		return e, nil
   295  	default:
   296  		return nil, fmt.Errorf("Irregal element was detected.")
   297  	}
   298  }
   299  
   300  // ネットワークを実行する。
   301  //
   302  // return : エラー情報。
   303  func (n *Network) Run() error {
   304  	if n.Start == nil {
   305  		return fmt.Errorf("Start element of network is nil.")
   306  	}
   307  
   308  	err := n.start()
   309  	if err != nil {
   310  		console.Display("CTM019E", err)
   311  		return err
   312  	}
   313  	console.Display("CTM012I", n.Name, n.ID)
   314  
   315  	return n.runNodes()
   316  }
   317  
   318  // ネットワークをリランする。
   319  //
   320  // return : エラー情報。
   321  func (n *Network) Rerun() error {
   322  	if n.Start == nil {
   323  		return fmt.Errorf("Start element of network is nil.")
   324  	}
   325  
   326  	err := n.resume()
   327  	if err != nil {
   328  		console.Display("CTM019E", err)
   329  		return err
   330  	}
   331  
   332  	prePID := n.Result.JobnetResult.PID
   333  	if util.IsProcessExists(prePID) {
   334  		return fmt.Errorf("JOBNETWORK [%d] still running.", n.ID)
   335  	}
   336  
   337  	console.Display("CTM012I", n.Name, n.ID)
   338  	n.setIsRerunJob()
   339  
   340  	return n.runNodes()
   341  }
   342  
   343  func (n *Network) runNodes() error {
   344  	current := n.Start
   345  	for {
   346  		next, err := current.Execute()
   347  		if err != nil {
   348  			return n.end(err)
   349  		}
   350  		if current == n.End {
   351  			return n.end(nil)
   352  		} else if next == nil {
   353  			err := fmt.Errorf("Element[id = %s] cannot terminate network because it is not a endEvent.", current.ID())
   354  			return n.end(err)
   355  		}
   356  		current = next
   357  	}
   358  	panic("Not reached.")
   359  }
   360  
   361  func (n *Network) setIsRerunJob() {
   362  	for _, e := range n.elements {
   363  		if j, ok := e.(*Job); ok {
   364  			if _, exists := n.Result.GetJobResults(j.id); exists {
   365  				j.IsRerunJob = true
   366  			}
   367  		}
   368  	}
   369  }
   370  
   371  // ジョブネットワークの開始処理
   372  func (n *Network) start() error {
   373  	timeout := config.Job.DefaultTimeoutMin * 60 * 1000
   374  	if timeout <= 0 {
   375  		timeout = 60000
   376  	}
   377  
   378  	err := n.globalLock.Lock(timeout)
   379  	if err != nil {
   380  		if err != util.ErrBusy {
   381  			return err
   382  		}
   383  		return fmt.Errorf("Lock Timeout.")
   384  	}
   385  	defer n.globalLock.Unlock()
   386  
   387  	n.Result, err = tx.StartJobNetwork(n.Name, config.DB.DBFile)
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	n.ID = n.Result.JobnetResult.ID
   393  	message.AddSysValue(`JOBNET`, `ID`, strconv.Itoa(n.ID))
   394  	message.AddSysValue(`JOBNET`, `SD`, n.Result.JobnetResult.StartDate)
   395  
   396  	return nil
   397  }
   398  
   399  // ジョブネットワークの再実行開始処理
   400  func (n *Network) resume() error {
   401  	timeout := config.Job.DefaultTimeoutMin * 60 * 1000
   402  	if timeout <= 0 {
   403  		timeout = 60000
   404  	}
   405  
   406  	err := n.globalLock.Lock(timeout)
   407  	if err != nil {
   408  		if err != util.ErrBusy {
   409  			return err
   410  		}
   411  		return fmt.Errorf("Lock Timeout.")
   412  	}
   413  	defer n.globalLock.Unlock()
   414  
   415  	n.Result, err = tx.ResumeJobNetwork(n.ID, config.DB.DBFile)
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	message.AddSysValue(`JOBNET`, `ID`, strconv.Itoa(n.ID))
   421  	message.AddSysValue(`JOBNET`, `SD`, n.Result.JobnetResult.StartDate)
   422  
   423  	return nil
   424  }
   425  
   426  // ジョブネットワークの終了処理
   427  func (n *Network) end(err error) error {
   428  	if err != nil {
   429  		n.Result.EndJobNetwork(db.ABNORMAL, err.Error())
   430  	} else {
   431  		n.Result.EndJobNetwork(db.NORMAL, "")
   432  	}
   433  	return err
   434  }
   435  
   436  // 終了処理を行う。
   437  func (n *Network) Terminate() {
   438  	n.globalLock.TermLock()
   439  }