зеркало из https://github.com/github/vitess-gh.git
Refactor workflow code to be more UI-friendly. (#2240)
The main goal of this change is to make not started and finished workflows visible in the UI. Without this it was possible to create a workflow with -skip_start option, and then it won't be visible in the UI thus not giving any ability to actually start it. Also once workflow finishes it was disappearing from the UI thus not giving any ability to see how it finished and whether it existed at all or not. Besides this UI problem also workflows were never deleted from the topo server and were accumulating indefinitely. This change makes everything from topo server visible in the UI thus giving ability for the user to delete already finished workflows. This UI improvement allows the validator workflow to become less awkward and actually finish once all fixers are done working. I'm modifying the validator workflow to do that here as well.
This commit is contained in:
Родитель
ed54a93a25
Коммит
30ee782478
|
@ -156,12 +156,18 @@ func (*SwapWorkflowFactory) Init(workflowProto *workflowpb.Workflow, args []stri
|
|||
|
||||
// Instantiate is a part of workflow.Factory interface. It instantiates workflow.Workflow object from
|
||||
// workflowpb.Workflow protobuf object.
|
||||
func (*SwapWorkflowFactory) Instantiate(workflowProto *workflowpb.Workflow) (workflow.Workflow, error) {
|
||||
func (*SwapWorkflowFactory) Instantiate(workflowProto *workflowpb.Workflow, rootNode *workflow.Node) (workflow.Workflow, error) {
|
||||
data := &swapWorkflowData{}
|
||||
if err := json.Unmarshal(workflowProto.Data, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Swap{keyspace: data.Keyspace, sql: data.SQL}, nil
|
||||
rootNode.Message = fmt.Sprintf("Schema swap is executed on the keyspace %s", data.Keyspace)
|
||||
|
||||
return &Swap{
|
||||
keyspace: data.Keyspace,
|
||||
sql: data.SQL,
|
||||
rootUINode: rootNode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run is a part of workflow.Workflow interface. This is the main entrance point of the schema swap workflow.
|
||||
|
@ -171,17 +177,9 @@ func (schemaSwap *Swap) Run(ctx context.Context, manager *workflow.Manager, work
|
|||
schemaSwap.topoServer = topo.GetServer()
|
||||
schemaSwap.tabletClient = tmclient.NewTabletManagerClient()
|
||||
|
||||
rootUINode := workflow.NewNode()
|
||||
rootUINode.PopulateFromWorkflow(workflowInfo)
|
||||
rootUINode.State = workflowpb.WorkflowState_Running
|
||||
rootUINode.Display = workflow.NodeDisplayIndeterminate
|
||||
rootUINode.Message = fmt.Sprintf("Schema swap is executed on the keyspace %s", schemaSwap.keyspace)
|
||||
if err := manager.NodeManager().AddRootNode(rootUINode); err != nil {
|
||||
return err
|
||||
}
|
||||
defer manager.NodeManager().RemoveRootNode(rootUINode)
|
||||
schemaSwap.rootUINode.Display = workflow.NodeDisplayIndeterminate
|
||||
schemaSwap.rootUINode.BroadcastChanges(false /* updateChildren */)
|
||||
|
||||
schemaSwap.rootUINode = rootUINode
|
||||
return schemaSwap.executeSwap()
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@ func init() {
|
|||
commandWorkflowStop,
|
||||
"<uuid>",
|
||||
"Stops the workflow."})
|
||||
addCommand(workflowsGroupName, command{
|
||||
"WorkflowDelete",
|
||||
commandWorkflowDelete,
|
||||
"<uuid>",
|
||||
"Deletes the finished or not started workflow."})
|
||||
addCommand(workflowsGroupName, command{
|
||||
"WorkflowWait",
|
||||
commandWorkflowWait,
|
||||
|
@ -119,6 +124,21 @@ func commandWorkflowStop(ctx context.Context, wr *wrangler.Wrangler, subFlags *f
|
|||
return WorkflowManager.Stop(ctx, uuid)
|
||||
}
|
||||
|
||||
func commandWorkflowDelete(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
|
||||
if WorkflowManager == nil {
|
||||
return fmt.Errorf("no workflow.Manager registered")
|
||||
}
|
||||
|
||||
if err := subFlags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if subFlags.NArg() != 1 {
|
||||
return fmt.Errorf("the <uuid> argument is required for the WorkflowDelete command")
|
||||
}
|
||||
uuid := subFlags.Arg(0)
|
||||
return WorkflowManager.Delete(ctx, uuid)
|
||||
}
|
||||
|
||||
func commandWorkflowWait(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
|
||||
if WorkflowManager == nil {
|
||||
return fmt.Errorf("no workflow.Manager registered")
|
||||
|
|
|
@ -42,8 +42,9 @@ type Factory interface {
|
|||
Init(w *workflowpb.Workflow, args []string) error
|
||||
|
||||
// Instantiate loads a workflow from the proto representation
|
||||
// into an in-memory Workflow object.
|
||||
Instantiate(w *workflowpb.Workflow) (Workflow, error)
|
||||
// into an in-memory Workflow object. rootNode is the root UI node
|
||||
// representing the workflow.
|
||||
Instantiate(w *workflowpb.Workflow, rootNode *Node) (Workflow, error)
|
||||
}
|
||||
|
||||
// Manager is the main Workflow manager object.
|
||||
|
@ -78,6 +79,9 @@ type runningWorkflow struct {
|
|||
// topo server.
|
||||
wi *topo.WorkflowInfo
|
||||
|
||||
// rootNode is the root UI node corresponding to this workflow.
|
||||
rootNode *Node
|
||||
|
||||
// cancel is the method associated with the context that runs
|
||||
// the workflow.
|
||||
cancel context.CancelFunc
|
||||
|
@ -178,34 +182,17 @@ func (m *Manager) loadAndStartJobsLocked() {
|
|||
log.Errorf("Failed to load workflow %v, will not start it: %v", uuid, err)
|
||||
continue
|
||||
}
|
||||
if wi.State != workflowpb.WorkflowState_Running {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ask the factory to create the running workflow.
|
||||
factory, ok := factories[wi.FactoryName]
|
||||
if !ok {
|
||||
log.Errorf("Saved workflow %v is using factory name %v but no such factory registered, will not start it", uuid, wi.FactoryName)
|
||||
continue
|
||||
}
|
||||
w, err := factory.Instantiate(wi.Workflow)
|
||||
rw, err := m.instantiateWorkflow(wi.Workflow)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to instantiate workflow %v from factory %v, will not start it: %v", uuid, wi.FactoryName, err)
|
||||
continue
|
||||
}
|
||||
rw.wi = wi
|
||||
|
||||
// Create a context to run it, and save the runningWorkflow.
|
||||
ctx, cancel := context.WithCancel(m.ctx)
|
||||
rw := &runningWorkflow{
|
||||
wi: wi,
|
||||
cancel: cancel,
|
||||
workflow: w,
|
||||
done: make(chan struct{}),
|
||||
if rw.wi.State == workflowpb.WorkflowState_Running {
|
||||
m.runWorkflow(rw)
|
||||
}
|
||||
m.workflows[uuid] = rw
|
||||
|
||||
// And run it in the background.
|
||||
go m.runWorkflow(ctx, uuid, rw)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,17 +222,53 @@ func (m *Manager) Create(ctx context.Context, factoryName string, args []string)
|
|||
if err := factory.Init(w, args); err != nil {
|
||||
return "", err
|
||||
}
|
||||
rw, err := m.instantiateWorkflow(w)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Now save the workflow in the topo server.
|
||||
_, err := m.ts.CreateWorkflow(ctx, w)
|
||||
rw.wi, err = m.ts.CreateWorkflow(ctx, w)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// And we're done.
|
||||
log.Infof("Created workflow %s (%s, %s)", w.Uuid, factoryName, w.Name)
|
||||
return w.Uuid, nil
|
||||
}
|
||||
|
||||
func (m *Manager) instantiateWorkflow(w *workflowpb.Workflow) (*runningWorkflow, error) {
|
||||
rw := &runningWorkflow{
|
||||
wi: &topo.WorkflowInfo{
|
||||
Workflow: w,
|
||||
},
|
||||
rootNode: NewNode(),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
rw.rootNode.Name = w.Name
|
||||
rw.rootNode.PathName = w.Uuid
|
||||
rw.rootNode.Path = "/" + rw.rootNode.PathName
|
||||
rw.rootNode.State = w.State
|
||||
|
||||
factory, ok := factories[w.FactoryName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no factory named %v is registered", w.FactoryName)
|
||||
}
|
||||
var err error
|
||||
rw.workflow, err = factory.Instantiate(w, rw.rootNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.workflows[w.Uuid] = rw
|
||||
if err := m.nodeManager.AddRootNode(rw.rootNode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
// Start will start a Workflow. It will load it in memory, update its
|
||||
// status to Running, and call its Run() method.
|
||||
func (m *Manager) Start(ctx context.Context, uuid string) error {
|
||||
|
@ -257,55 +280,40 @@ func (m *Manager) Start(ctx context.Context, uuid string) error {
|
|||
return fmt.Errorf("manager not running")
|
||||
}
|
||||
|
||||
// Make sure it is not running already.
|
||||
if _, ok := m.workflows[uuid]; ok {
|
||||
return fmt.Errorf("workflow %v is already running", uuid)
|
||||
rw, ok := m.workflows[uuid]
|
||||
if !ok {
|
||||
return fmt.Errorf("Cannot find workflow %v in the workflow list", uuid)
|
||||
}
|
||||
|
||||
// Load it from the topo server, make sure it has the right state.
|
||||
wi, err := m.ts.GetWorkflow(ctx, uuid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wi.State != workflowpb.WorkflowState_NotStarted {
|
||||
return fmt.Errorf("workflow with uuid %v is in state %v", uuid, wi.State)
|
||||
}
|
||||
factory, ok := factories[wi.FactoryName]
|
||||
if !ok {
|
||||
return fmt.Errorf("workflow %v is using factory name %v but no such factory registered", uuid, wi.FactoryName)
|
||||
if rw.wi.State != workflowpb.WorkflowState_NotStarted {
|
||||
return fmt.Errorf("workflow with uuid %v is in state %v", uuid, rw.wi.State)
|
||||
}
|
||||
|
||||
// Change its state in the topo server. Note we do that first,
|
||||
// so if the running part fails, we will retry next time.
|
||||
wi.State = workflowpb.WorkflowState_Running
|
||||
wi.StartTime = time.Now().Unix()
|
||||
if err := m.ts.SaveWorkflow(ctx, wi); err != nil {
|
||||
rw.wi.State = workflowpb.WorkflowState_Running
|
||||
rw.wi.StartTime = time.Now().Unix()
|
||||
if err := m.ts.SaveWorkflow(ctx, rw.wi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ask the factory to create the running workflow.
|
||||
w, err := factory.Instantiate(wi.Workflow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a context to run it, and save the runningWorkflow.
|
||||
ctx, cancel := context.WithCancel(m.ctx)
|
||||
rw := &runningWorkflow{
|
||||
wi: wi,
|
||||
cancel: cancel,
|
||||
workflow: w,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
m.workflows[uuid] = rw
|
||||
|
||||
// And run it in the background.
|
||||
go m.runWorkflow(ctx, uuid, rw)
|
||||
rw.rootNode.State = workflowpb.WorkflowState_Running
|
||||
rw.rootNode.BroadcastChanges(false /* updateChildren */)
|
||||
|
||||
m.runWorkflow(rw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkflow) {
|
||||
func (m *Manager) runWorkflow(rw *runningWorkflow) {
|
||||
// Create a context to run it.
|
||||
var ctx context.Context
|
||||
ctx, rw.cancel = context.WithCancel(m.ctx)
|
||||
|
||||
// And run it in the background.
|
||||
go m.executeWorkflowRun(ctx, rw)
|
||||
}
|
||||
|
||||
func (m *Manager) executeWorkflowRun(ctx context.Context, rw *runningWorkflow) {
|
||||
defer close(rw.done)
|
||||
|
||||
// Run will block until one of three things happen:
|
||||
|
@ -344,9 +352,7 @@ func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkf
|
|||
}
|
||||
|
||||
// We are not shutting down, but this workflow is done, or
|
||||
// canceled. In any case, delete it from the running
|
||||
// workflows, and change its topo Server state.
|
||||
delete(m.workflows, uuid)
|
||||
// canceled. In any case, change its topo Server state.
|
||||
rw.wi.State = workflowpb.WorkflowState_Done
|
||||
if err != nil {
|
||||
rw.wi.Error = err.Error()
|
||||
|
@ -355,6 +361,9 @@ func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkf
|
|||
if err := m.ts.SaveWorkflow(m.ctx, rw.wi); err != nil {
|
||||
log.Errorf("Could not save workflow %v after completion: %v", rw.wi, err)
|
||||
}
|
||||
|
||||
rw.rootNode.State = workflowpb.WorkflowState_Done
|
||||
rw.rootNode.BroadcastChanges(false /* updateChildren */)
|
||||
}
|
||||
|
||||
// Stop stops the running workflow. It will cancel its context and
|
||||
|
@ -381,6 +390,26 @@ func (m *Manager) Stop(ctx context.Context, uuid string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the finished or not started workflow.
|
||||
func (m *Manager) Delete(ctx context.Context, uuid string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
rw, ok := m.workflows[uuid]
|
||||
if !ok {
|
||||
return fmt.Errorf("No workflow with uuid %v", uuid)
|
||||
}
|
||||
if rw.wi.State == workflowpb.WorkflowState_Running {
|
||||
return fmt.Errorf("Cannot delete running workflow")
|
||||
}
|
||||
if err := m.ts.DeleteWorkflow(m.ctx, rw.wi); err != nil {
|
||||
log.Errorf("Could not delete workflow %v: %v", rw.wi, err)
|
||||
}
|
||||
m.nodeManager.RemoveRootNode(rw.rootNode)
|
||||
delete(m.workflows, uuid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the provided workflow to end.
|
||||
func (m *Manager) Wait(ctx context.Context, uuid string) error {
|
||||
// Find the workflow.
|
||||
|
|
|
@ -94,6 +94,8 @@ func TestManagerRestart(t *testing.T) {
|
|||
// Stop the manager.
|
||||
cancel()
|
||||
wg.Wait()
|
||||
// Recreate the manager immitating restart.
|
||||
m = NewManager(ts)
|
||||
|
||||
// Make sure the workflow is still in the topo server. This
|
||||
// validates that interrupting the Manager leaves the jobs in
|
||||
|
|
|
@ -11,8 +11,6 @@ import (
|
|||
log "github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/youtube/vitess/go/vt/topo"
|
||||
|
||||
workflowpb "github.com/youtube/vitess/go/vt/proto/workflow"
|
||||
)
|
||||
|
||||
|
@ -173,13 +171,6 @@ func NewNode() *Node {
|
|||
}
|
||||
}
|
||||
|
||||
// PopulateFromWorkflow populates node's path and name from the workflow information.
|
||||
func (n *Node) PopulateFromWorkflow(wi *topo.WorkflowInfo) {
|
||||
n.Name = wi.Name
|
||||
n.PathName = wi.Uuid
|
||||
n.Path = "/" + n.PathName
|
||||
}
|
||||
|
||||
// BroadcastChanges sends the new contents of the node to the watchers.
|
||||
func (n *Node) BroadcastChanges(updateChildren bool) error {
|
||||
n.nodeManager.mu.Lock()
|
||||
|
|
|
@ -69,12 +69,9 @@ func (sw *SleepWorkflow) Run(ctx context.Context, manager *Manager, wi *topo.Wor
|
|||
sw.mu.Lock()
|
||||
sw.manager = manager
|
||||
sw.wi = wi
|
||||
sw.node = NewNode()
|
||||
sw.node.PopulateFromWorkflow(wi)
|
||||
|
||||
sw.node.Listener = sw
|
||||
sw.node.State = workflowpb.WorkflowState_Running
|
||||
sw.node.Display = NodeDisplayDeterminate
|
||||
sw.node.Message = "This workflow is a test workflow that just sleeps for the provided amount of time."
|
||||
sw.node.Actions = []*Action{
|
||||
{
|
||||
Name: pauseAction,
|
||||
|
@ -88,10 +85,7 @@ func (sw *SleepWorkflow) Run(ctx context.Context, manager *Manager, wi *topo.Wor
|
|||
},
|
||||
}
|
||||
sw.uiUpdateLocked()
|
||||
if err := manager.NodeManager().AddRootNode(sw.node); err != nil {
|
||||
return err
|
||||
}
|
||||
defer manager.NodeManager().RemoveRootNode(sw.node)
|
||||
sw.node.BroadcastChanges(false /* updateChildren */)
|
||||
sw.mu.Unlock()
|
||||
|
||||
for {
|
||||
|
@ -221,13 +215,16 @@ func (f *SleepWorkflowFactory) Init(w *workflowpb.Workflow, args []string) error
|
|||
}
|
||||
|
||||
// Instantiate is part of the workflow.Factory interface.
|
||||
func (f *SleepWorkflowFactory) Instantiate(w *workflowpb.Workflow) (Workflow, error) {
|
||||
func (f *SleepWorkflowFactory) Instantiate(w *workflowpb.Workflow, rootNode *Node) (Workflow, error) {
|
||||
rootNode.Message = "This workflow is a test workflow that just sleeps for the provided amount of time."
|
||||
|
||||
data := &SleepWorkflowData{}
|
||||
if err := json.Unmarshal(w.Data, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SleepWorkflow{
|
||||
data: data,
|
||||
node: rootNode,
|
||||
logger: logutil.NewMemoryLogger(),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ package topovalidator
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -68,6 +69,12 @@ type Workflow struct {
|
|||
|
||||
// logger is the logger we export UI logs from.
|
||||
logger *logutil.MemoryLogger
|
||||
|
||||
// rootUINode is the root node representing the workflow in the UI.
|
||||
rootUINode *workflow.Node
|
||||
|
||||
// wg is a wait group that will be used to wait for all fixers to be done.
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// workflowFixer contains all the information about a fixer.
|
||||
|
@ -77,6 +84,7 @@ type workflowFixer struct {
|
|||
fixer Fixer
|
||||
actions []string
|
||||
node *workflow.Node
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// AddFixer adds a Fixer to the Workflow. It will end up displaying a
|
||||
|
@ -88,28 +96,21 @@ func (w *Workflow) AddFixer(name, message string, fixer Fixer, actions []string)
|
|||
message: message,
|
||||
fixer: fixer,
|
||||
actions: actions,
|
||||
wg: &w.wg,
|
||||
})
|
||||
}
|
||||
|
||||
// Run is part of the workflow.Workflow interface.
|
||||
func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.WorkflowInfo) error {
|
||||
// Create a UI Node.
|
||||
node := workflow.NewNode()
|
||||
node.PopulateFromWorkflow(wi)
|
||||
node.State = workflowpb.WorkflowState_Running
|
||||
node.Display = workflow.NodeDisplayDeterminate
|
||||
node.Message = "Validates the Topology and proposes fixes for known issues."
|
||||
w.uiUpdate(node)
|
||||
if err := manager.NodeManager().AddRootNode(node); err != nil {
|
||||
return err
|
||||
}
|
||||
defer manager.NodeManager().RemoveRootNode(node)
|
||||
w.uiUpdate()
|
||||
w.rootUINode.Display = workflow.NodeDisplayDeterminate
|
||||
w.rootUINode.BroadcastChanges(false /* updateChildren */)
|
||||
|
||||
// Run all the validators. They may add fixers.
|
||||
for name, v := range validators {
|
||||
w.logger.Infof("Running validator: %v", name)
|
||||
w.uiUpdate(node)
|
||||
node.BroadcastChanges(false /* updateChildren */)
|
||||
w.uiUpdate()
|
||||
w.rootUINode.BroadcastChanges(false /* updateChildren */)
|
||||
err := v.Audit(ctx, manager.TopoServer(), w)
|
||||
if err != nil {
|
||||
w.logger.Errorf("Validator %v failed: %v", name, err)
|
||||
|
@ -124,8 +125,9 @@ func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.
|
|||
w.logger.Errorf("No problem found")
|
||||
}
|
||||
for i, f := range w.fixers {
|
||||
w.wg.Add(1)
|
||||
f.node = workflow.NewNode()
|
||||
node.Children = append(node.Children, f.node)
|
||||
w.rootUINode.Children = append(w.rootUINode.Children, f.node)
|
||||
f.node.PathName = fmt.Sprintf("%v", i)
|
||||
f.node.Name = f.name
|
||||
f.node.Message = f.message
|
||||
|
@ -137,23 +139,30 @@ func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.
|
|||
}
|
||||
f.node.Listener = f
|
||||
}
|
||||
w.uiUpdate(node)
|
||||
node.BroadcastChanges(true /* updateChildren */)
|
||||
w.uiUpdate()
|
||||
w.rootUINode.BroadcastChanges(true /* updateChildren */)
|
||||
|
||||
// And wait for the workflow to be terminated.
|
||||
// And wait for the workflow to be done.
|
||||
fixersChan := make(chan struct{})
|
||||
go func(wg *sync.WaitGroup) {
|
||||
wg.Wait()
|
||||
close(fixersChan)
|
||||
}(&w.wg)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-fixersChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// uiUpdate updates the computed parts of the Node, based on the
|
||||
// current state. Needs to be called from inside node.Modify.
|
||||
func (w *Workflow) uiUpdate(node *workflow.Node) {
|
||||
// current state.
|
||||
func (w *Workflow) uiUpdate() {
|
||||
c := len(validators)
|
||||
node.Progress = 100 * w.runCount / c
|
||||
node.ProgressMessage = fmt.Sprintf("%v/%v", w.runCount, c)
|
||||
node.Log = w.logger.String()
|
||||
w.rootUINode.Progress = 100 * w.runCount / c
|
||||
w.rootUINode.ProgressMessage = fmt.Sprintf("%v/%v", w.runCount, c)
|
||||
w.rootUINode.Log = w.logger.String()
|
||||
}
|
||||
|
||||
// Action is part of the workflow.ActionListener interface.
|
||||
|
@ -173,6 +182,7 @@ func (f *workflowFixer) Action(ctx context.Context, path, name string) error {
|
|||
f.node.Message = "Addressed(" + name + "): " + f.node.Message
|
||||
f.node.Display = workflow.NodeDisplayNone
|
||||
f.node.BroadcastChanges(false /* updateChildren */)
|
||||
f.wg.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -191,8 +201,11 @@ func (f *WorkflowFactory) Init(w *workflowpb.Workflow, args []string) error {
|
|||
}
|
||||
|
||||
// Instantiate is part of the workflow.Factory interface.
|
||||
func (f *WorkflowFactory) Instantiate(w *workflowpb.Workflow) (workflow.Workflow, error) {
|
||||
func (f *WorkflowFactory) Instantiate(w *workflowpb.Workflow, rootNode *workflow.Node) (workflow.Workflow, error) {
|
||||
rootNode.Message = "Validates the Topology and proposes fixes for known issues."
|
||||
|
||||
return &Workflow{
|
||||
logger: logutil.NewMemoryLogger(),
|
||||
logger: logutil.NewMemoryLogger(),
|
||||
rootUINode: rootNode,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -28,5 +28,5 @@
|
|||
</head>
|
||||
<body class="flex-column">
|
||||
<vt-app-root class="flex-column flex-grow">Loading...</vt-app-root>
|
||||
<script type="text/javascript" src="inline.js"></script><script type="text/javascript" src="styles.07f8743f5392cfdfbcb5.bundle.js"></script><script type="text/javascript" src="main.8c655da3f055985098e2.bundle.js"></script></body>
|
||||
<script type="text/javascript" src="inline.js"></script><script type="text/javascript" src="styles.07f8743f5392cfdfbcb5.bundle.js"></script><script type="text/javascript" src="main.173f8ea8a9d98ab9592e.bundle.js"></script></body>
|
||||
</html>
|
||||
|
|
|
@ -1 +1 @@
|
|||
!function(e){function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,o,c){for(var _,a,i,u=0,p=[];u<t.length;u++)a=t[u],n[a]&&p.push(n[a][0]),n[a]=0;for(_ in o)if(Object.prototype.hasOwnProperty.call(o,_)){var f=o[_];switch(typeof f){case"object":e[_]=function(r){var t=r.slice(1),n=r[0];return function(r,o,c){e[n].apply(this,[r,o,c].concat(t))}}(f);break;case"function":e[_]=f;break;default:e[_]=e[f]}}for(r&&r(t,o,c);p.length;)p.shift()();if(c)for(u=0;u<c.length;u++)i=__webpack_require__(__webpack_require__.s=c[u]);return i};var t={},n={2:0};__webpack_require__.e=function(e){function onScriptComplete(){t.onerror=t.onload=null,clearTimeout(o);var r=n[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),n[e]=void 0)}if(0===n[e])return Promise.resolve();if(n[e])return n[e][2];var r=document.getElementsByTagName("head")[0],t=document.createElement("script");t.type="text/javascript",t.charset="utf-8",t.async=!0,t.timeout=12e4,t.src=__webpack_require__.p+""+e+"."+{0:"8c655da3f055985098e2",1:"07f8743f5392cfdfbcb5"}[e]+".chunk.js";var o=setTimeout(onScriptComplete,12e4);t.onerror=t.onload=onScriptComplete,r.appendChild(t);var c=new Promise(function(r,t){n[e]=[r,t]});return n[e][2]=c},__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,r,t){Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="",__webpack_require__.oe=function(e){throw console.error(e),e}}(function(e){for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))switch(typeof e[r]){case"function":break;case"object":e[r]=function(r){var t=r.slice(1),n=e[r[0]];return function(e,r,o){n.apply(this,[e,r,o].concat(t))}}(e[r]);break;default:e[r]=e[e[r]]}return e}([]));
|
||||
!function(e){function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,o,c){for(var _,a,i,u=0,p=[];u<t.length;u++)a=t[u],n[a]&&p.push(n[a][0]),n[a]=0;for(_ in o)if(Object.prototype.hasOwnProperty.call(o,_)){var f=o[_];switch(typeof f){case"object":e[_]=function(r){var t=r.slice(1),n=r[0];return function(r,o,c){e[n].apply(this,[r,o,c].concat(t))}}(f);break;case"function":e[_]=f;break;default:e[_]=e[f]}}for(r&&r(t,o,c);p.length;)p.shift()();if(c)for(u=0;u<c.length;u++)i=__webpack_require__(__webpack_require__.s=c[u]);return i};var t={},n={2:0};__webpack_require__.e=function(e){function onScriptComplete(){t.onerror=t.onload=null,clearTimeout(o);var r=n[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),n[e]=void 0)}if(0===n[e])return Promise.resolve();if(n[e])return n[e][2];var r=document.getElementsByTagName("head")[0],t=document.createElement("script");t.type="text/javascript",t.charset="utf-8",t.async=!0,t.timeout=12e4,t.src=__webpack_require__.p+""+e+"."+{0:"173f8ea8a9d98ab9592e",1:"07f8743f5392cfdfbcb5"}[e]+".chunk.js";var o=setTimeout(onScriptComplete,12e4);t.onerror=t.onload=onScriptComplete,r.appendChild(t);var c=new Promise(function(r,t){n[e]=[r,t]});return n[e][2]=c},__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,r,t){Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="",__webpack_require__.oe=function(e){throw console.error(e),e}}(function(e){for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))switch(typeof e[r]){case"function":break;case"object":e[r]=function(r){var t=r.slice(1),n=e[r[0]];return function(e,r,o){n.apply(this,[e,r,o].concat(t))}}(e[r]);break;default:e[r]=e[e[r]]}return e}([]));
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Двоичный файл не отображается.
Двоичные данные
web/vtctld2/app/main.8c655da3f055985098e2.bundle.js.gz
Двоичные данные
web/vtctld2/app/main.8c655da3f055985098e2.bundle.js.gz
Двоичный файл не отображается.
|
@ -1,6 +1,4 @@
|
|||
<div *ngIf="redirect === ''" class="vt-toolbar vt-padding">
|
||||
<md-icon class="vt-menu" (click)="menu.toggle($event)">menu</md-icon>
|
||||
<p-menu #menu popup="popup" [model]="actions"></p-menu>
|
||||
<h1 class="vt-title">{{title}}</h1>
|
||||
</div>
|
||||
<div *ngIf="redirect !== ''" class="vt-toolbar vt-padding">
|
||||
|
|
|
@ -11,8 +11,6 @@ import { DialogSettings } from '../shared/dialog/dialog-settings';
|
|||
import { PrepareResponse } from '../shared/prepare-response';
|
||||
import { NewWorkflowFlags } from '../shared/flags/workflow.flags';
|
||||
|
||||
import { MenuItem } from 'primeng/primeng';
|
||||
|
||||
@Component({
|
||||
selector: 'vt-tasks',
|
||||
templateUrl: './workflow-list.component.html',
|
||||
|
@ -25,7 +23,7 @@ import { MenuItem } from 'primeng/primeng';
|
|||
})
|
||||
|
||||
export class WorkflowListComponent implements OnDestroy, OnInit {
|
||||
title = 'Running Workflows';
|
||||
title = 'Workflows';
|
||||
redirect = '';
|
||||
workflows = [
|
||||
new Node('Horizontal Resharding Workflow', '/UU130429', [
|
||||
|
@ -60,8 +58,6 @@ export class WorkflowListComponent implements OnDestroy, OnInit {
|
|||
];
|
||||
dialogSettings: DialogSettings;
|
||||
dialogContent: DialogContent;
|
||||
private actions: MenuItem[];
|
||||
private running = true;
|
||||
|
||||
constructor(private workflowService: WorkflowService) {}
|
||||
|
||||
|
@ -71,7 +67,6 @@ export class WorkflowListComponent implements OnDestroy, OnInit {
|
|||
});
|
||||
this.dialogContent = new DialogContent();
|
||||
this.dialogSettings = new DialogSettings();
|
||||
this.actions = [{label: 'Toggle Running / Non-running Workflows', command: (event) => {this.toggleRunning(); }}];
|
||||
|
||||
// Resharding Workflow Example
|
||||
this.updateWorkFlow('/UU130429', {
|
||||
|
@ -398,15 +393,5 @@ dolore magnam aliquam quaerat voluptatem.'});
|
|||
canDeactivate(): Observable<boolean> | boolean {
|
||||
return !this.dialogSettings.pending;
|
||||
}
|
||||
|
||||
toggleRunning() {
|
||||
if (this.running) {
|
||||
this.running = false;
|
||||
this.title = 'Stopped Workflows';
|
||||
} else {
|
||||
this.running = true;
|
||||
this.title = 'Running Workflows';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<span class="vt-workflow-action">
|
||||
<button md-raised-button *ngIf="workflow.isNotStarted()" (click)="startClicked($event); false">Start</button>
|
||||
<button md-raised-button *ngIf="workflow.isRunning()" (click)="stopClicked($event); false">Stop</button>
|
||||
<button md-raised-button *ngIf="workflow.isDone()" (click)="deleteClicked($event); false">Delete</button>
|
||||
<button md-raised-button *ngIf="!workflow.isRunning()" (click)="deleteClicked($event); false">Delete</button>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -80,7 +80,12 @@ export class WorkflowComponent implements OnInit {
|
|||
// (click)="startClicked($event); false".
|
||||
startClicked(event) {
|
||||
event.stopPropagation();
|
||||
console.log('startClicked(%s)', this.workflow.path);
|
||||
this.dialogSettings = new DialogSettings('Start', `Start ${this.workflow.name}`,
|
||||
`Are you sure you want to start ${this.workflow.name}?`,
|
||||
`There was a problem starting ${this.workflow.name}:`);
|
||||
let flags = new WorkflowFlags(this.workflow.getId()).flags;
|
||||
this.dialogContent = new DialogContent('workflow_uuid', flags, {}, undefined, 'WorkflowStart');
|
||||
this.dialogSettings.toggleModal();
|
||||
}
|
||||
|
||||
stopClicked(event) {
|
||||
|
@ -95,6 +100,11 @@ export class WorkflowComponent implements OnInit {
|
|||
|
||||
deleteClicked(event) {
|
||||
event.stopPropagation();
|
||||
console.log('deleteClicked(%s)', this.workflow.path);
|
||||
this.dialogSettings = new DialogSettings('Delete', `Delete ${this.workflow.name}`,
|
||||
`Are you sure you want to delete ${this.workflow.name}?`,
|
||||
`There was a problem deleting ${this.workflow.name}:`);
|
||||
let flags = new WorkflowFlags(this.workflow.getId()).flags;
|
||||
this.dialogContent = new DialogContent('workflow_uuid', flags, {}, undefined, 'WorkflowDelete');
|
||||
this.dialogSettings.toggleModal();
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче