@@ -2,13 +2,17 @@ package azkaban
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
7+ htmlx "golang.org/x/net/html"
68 "html"
79 "io"
810 "io/ioutil"
911 "log"
1012 "net/http"
1113 "net/http/httputil"
14+ "strconv"
15+ "time"
1216)
1317
1418type Client struct {
@@ -129,7 +133,7 @@ func (c *Client) RestartFlowNow(project, flow string) error {
129133 return nil
130134}
131135
132- func (c * Client ) FlowEcecutionStatus (executionID int64 ) (FlowExecutionStatus , error ) {
136+ func (c * Client ) FlowExecutionStatus (executionID int64 ) (FlowExecutionStatus , error ) {
133137 status := FlowExecutionStatus {}
134138
135139 params := make (map [string ]string )
@@ -182,6 +186,120 @@ func (c *Client) FlowSchedule(projectID int64, flowID string) (FlowSchedule, err
182186 }
183187}
184188
189+ func (c * Client ) Running () ([]FlowExecution , error ) {
190+ resp , err := c .request ("GET" , "executor" , nil )
191+ if err != nil {
192+ return nil , err
193+ }
194+ if resp .StatusCode != 200 {
195+ return nil , errors .New (fmt .Sprintf ("got %d %s when retrieving running executions" , resp .StatusCode , resp .Status ))
196+ }
197+
198+ defer resp .Body .Close ()
199+ doc , err := htmlx .Parse (resp .Body )
200+ if err != nil {
201+ return nil , err
202+ }
203+
204+ // Azkaban serves the login page simply with a HTTP 200 so the only way to check if we're looking at the login page
205+ // is by looking for the login element.
206+ if findElementWithID (doc ,"username" ) != nil && findElementWithID (doc , "password" ) != nil {
207+ return nil , errors .New ("credentials expired, reauthenticate" )
208+ }
209+
210+ table := findElementWithID (doc , "executingJobs" )
211+
212+ return findExecutions (table )
213+ }
214+
215+ type FlowExecution struct {
216+ FlowID string
217+ Project string
218+ Execution
219+ }
220+
221+ func findExecutions (n * htmlx.Node ) ([]FlowExecution , error ) {
222+ tbody := findElementsOfType (n , "tbody" )
223+ rows := findElementsOfType (tbody [0 ], "tr" )
224+
225+ var executions []FlowExecution
226+ for _ , row := range rows {
227+ cells := findElementsOfType (row , "td" )
228+
229+ execID , err := strconv .ParseInt (findElementsOfType (cells [1 ], "a" )[0 ].FirstChild .Data , 10 , 64 )
230+ if err != nil {
231+ return nil , err
232+ }
233+ flowID := findElementsOfType (cells [3 ], "a" )[0 ].FirstChild .Data
234+ project := findElementsOfType (cells [4 ], "a" )[0 ].FirstChild .Data
235+ // TODO project ID is not part of the actual link so we can't parse it. We'll have to fetch projects before we do this
236+ // parsing time "2019-08-21 01:53:42" as "Jan 2, 2006 at 3:04pm": cannot parse "2019-08-21 01:53:42" as "Jan"
237+ startTime , err := time .Parse ("2006-01-02 15:04:05" , cells [7 ].FirstChild .Data )
238+ if err != nil {
239+ return nil , err
240+ }
241+
242+ execution := FlowExecution {
243+ FlowID : flowID ,
244+ Project : project ,
245+ Execution : Execution {
246+ SubmitTime : AzkabanTimestamp {},
247+ StartTime : AzkabanTimestamp (startTime ),
248+ Status : "RUNNING" ,
249+ ID : execID ,
250+ EndTime : AzkabanTimestamp (time .Now ()),
251+ },
252+ }
253+
254+ executions = append (executions , execution )
255+ }
256+
257+ return executions , nil
258+ }
259+
260+ func getAttribute (n * htmlx.Node , name string ) string {
261+ for _ , a := range n .Attr {
262+ if a .Key == name {
263+ return a .Val
264+ }
265+ }
266+ return ""
267+ }
268+
269+ func findElementsOfType (n * htmlx.Node , t string ) []* htmlx.Node {
270+ var result []* htmlx.Node
271+ if n .Type == htmlx .ElementNode && n .Data == t {
272+ result = append (result , n )
273+ }
274+
275+ for c := n .FirstChild ; c != nil ; c = c .NextSibling {
276+ result = append (result , findElementsOfType (c , t )... )
277+ }
278+
279+ return result
280+ }
281+
282+ func findElementWithID (n * htmlx.Node , id string ) * htmlx.Node {
283+ if hasAttribute (n , "id" , id ) {
284+ return n ;
285+ }
286+ for c := n .FirstChild ; c != nil ; c = c .NextSibling {
287+ if result := findElementWithID (c , id ); result != nil {
288+ return result
289+ }
290+ }
291+ return nil
292+ }
293+
294+ func hasAttribute (n * htmlx.Node , name string , value string ) bool {
295+ for _ , a := range n .Attr {
296+ if a .Key == name && a .Val == value {
297+ return true
298+ }
299+ }
300+ return false
301+ }
302+
185303func (c * Client ) requestAndDecode (method string , path string , params map [string ]string , dst interface {}) error {
186304 resp , err := c .request (method , path , params )
187305 if err != nil {
0 commit comments