run a juttle program through the juttle streaming API and return the
    various events that are part of running a Juttle program which include:

        * Initial job status details including information to associate
          multiple flowgraphs with their individual outputs (sinks):
          {
            "status": "ok",
            "job": {
              "channel_id": "56bde5f0",
              "_start_time": "2015-10-03T06:59:49.233Z",
              "alias": "jut-tools program 1443855588",
              "_ms_begin": 1443855589233,
              "user": "0fbbd98d-cf33-4582-8ca1-15a3d3fee510",
              "timeout": 5,
              "id": "b973bce6"
            },
            "now": "2015-10-03T06:59:49.230Z",
            "stats": ...
            "sinks": [
              {
                "location": {
                  "start": {
                    "column": 17,
                    "line": 1,
                    "offset": 16
                  },
                  "end": {
                    "column": 24,
                    "line": 1,
                    "offset": 23
                  },
                  "filename": "main"
                },
                "name": "table",
                "channel": "sink237",
                "options": {
                  "_jut_time_bounds": []
                }
              },
              ... as many sinks as there are flowgrpahs in your program
            ]
           }

        * Each set of points returned along with the indication of which sink
          they belong to:
          {
            "points": [ array of points ],
            "sink": sink_id
          }

        * Error event indicating where in your program the error occurred
          {
            "error": true,
            payload with "info" and "context" explaining exact error
          }

        * Warning event indicating where in your program the error occurred
          {
            "warning": true,
            payload with "info" and "context" explaining exact warning
          }

        * ...

    juttle: juttle program to execute
    deployment_name: the deployment name to execute the program on
    persist: if set to True then we won't wait for response data and will
             disconnect from the websocket leaving the program running in
             the background if it is uses a background output
             (http://docs.jut.io/juttle-guide/#background_outputs) and
             therefore becomes a persistent job.
    token_manager: auth.TokenManager object
    app_url: optional argument used primarily for internal Jut testing

def run(juttle,
        deployment_name,
        program_name=None,
        persist=False,
        token_manager=None,
        app_url=defaults.APP_URL):
    """
    run a juttle program through the juttle streaming API and return the
    various events that are part of running a Juttle program which include:

        * Initial job status details including information to associate
          multiple flowgraphs with their individual outputs (sinks):
          {
            "status": "ok",
            "job": {
              "channel_id": "56bde5f0",
              "_start_time": "2015-10-03T06:59:49.233Z",
              "alias": "jut-tools program 1443855588",
              "_ms_begin": 1443855589233,
              "user": "0fbbd98d-cf33-4582-8ca1-15a3d3fee510",
              "timeout": 5,
              "id": "b973bce6"
            },
            "now": "2015-10-03T06:59:49.230Z",
            "stats": ...
            "sinks": [
              {
                "location": {
                  "start": {
                    "column": 17,
                    "line": 1,
                    "offset": 16
                  },
                  "end": {
                    "column": 24,
                    "line": 1,
                    "offset": 23
                  },
                  "filename": "main"
                },
                "name": "table",
                "channel": "sink237",
                "options": {
                  "_jut_time_bounds": []
                }
              },
              ... as many sinks as there are flowgrpahs in your program
            ]
           }

        * Each set of points returned along with the indication of which sink
          they belong to:
          {
            "points": [ array of points ],
            "sink": sink_id
          }

        * Error event indicating where in your program the error occurred
          {
            "error": true,
            payload with "info" and "context" explaining exact error
          }

        * Warning event indicating where in your program the error occurred
          {
            "warning": true,
            payload with "info" and "context" explaining exact warning
          }

        * ...

    juttle: juttle program to execute
    deployment_name: the deployment name to execute the program on
    persist: if set to True then we won't wait for response data and will
             disconnect from the websocket leaving the program running in
             the background if it is uses a background output
             (http://docs.jut.io/juttle-guide/#background_outputs) and
             therefore becomes a persistent job.
    token_manager: auth.TokenManager object
    app_url: optional argument used primarily for internal Jut testing
    """
    headers = token_manager.get_access_token_headers()

    data_url = get_juttle_data_url(deployment_name,
                                   app_url=app_url,
                                   token_manager=token_manager)

    websocket = __wss_connect(data_url, token_manager)

    data = websocket.recv()
    channel_id_obj = json.loads(data)

    if is_debug_enabled():
        debug('got channel response %s', json.dumps(channel_id_obj))

    channel_id = channel_id_obj['channel_id']
    juttle_job = {
        'channel_id': channel_id,
        'alias': program_name,
        'program': juttle
    }

    response = requests.post('%s/api/v1/jobs' % data_url,
                             data=json.dumps(juttle_job),
                             headers=headers)

    if response.status_code != 200:
        yield {
            "error": True,
            "context": response.json()
        }
        return

    job_info = response.json()
    # yield job_info so the caller to this method can figure out which sinks
    # correlate to which flowgraphs
    yield job_info
    job_id = job_info['job']['id']

    if is_debug_enabled():
        debug('started job %s', json.dumps(job_info))

    for data in connect_job(job_id,
                            deployment_name,
                            token_manager=token_manager,
                            app_url=app_url,
                            persist=persist,
                            websocket=websocket,
                            data_url=data_url):
        yield data