/**
 * The "noit_livestream" protocol requires a well formed request to come
 * in via websocket that resembles:
 *
 * {
 *   "period_ms": <milliseconds>,
 *   "check_uuid": "<uuid_string>",
 *   "metrics" : ["<metric_name1>", "<metric_name2>"]
 * }
 *
 * There can be only one active livestream request for a single websocket at one time.
 * However, you can send down a new livestream request at any time to alter the
 * stream based on new requirements.
 *
 * If the "metrics" member exists in the incoming request, then the metrics that are sent will be
 * filtered to those that match the strings in the array by exact match.  If you want to get
 * all metrics in the check, omit the "metrics" member from the request object.
 */
int
noit_websocket_msg_handler(mtev_http_rest_closure_t *restc, int opcode,
                           const unsigned char *msg, size_t msg_len)
{
#ifdef HAVE_WSLAY
  const char *error = NULL;
  noit_websocket_closure_t *handler_data = restc->call_closure;

  /* for now this message is JSON, that might change in the future */

  struct mtev_json_tokener *tok = mtev_json_tokener_new();
  struct mtev_json_object *request = mtev_json_tokener_parse_ex(tok, (const char *)msg, msg_len);
  enum mtev_json_tokener_error err = tok->err;
  mtev_json_tokener_free(tok);
  if (err != mtev_json_tokener_success) {
    error = "Unable to parse incoming json";
    goto websocket_handler_error;
  }

  uint64_t period_ms = mtev_json_object_get_uint64(mtev_json_object_object_get(request, "period_ms"));
  const char *check_uuid = mtev_json_object_get_string(mtev_json_object_object_get(request, "check_uuid"));
  struct mtev_json_object *metrics = mtev_json_object_object_get(request, "metrics");

  if (handler_data != NULL) {
    /* destroy old subscription */
    noit_websocket_closure_free(handler_data);
    handler_data = NULL;
  }

  handler_data = restc->call_closure = noit_websocket_closure_alloc();
  handler_data->use_filter = mtev_false;
  restc->call_closure_free = noit_websocket_closure_free;
  handler_data->period = period_ms;
  if (metrics != NULL) {
    handler_data->use_filter = mtev_true;
    handler_data->filter_count = mtev_json_object_array_length(metrics);
    handler_data->filters = calloc(sizeof(char *), handler_data->filter_count);
    jl_array_list* a = mtev_json_object_get_array(metrics);
    for (int i = 0; i < handler_data->filter_count; i++) {
      struct mtev_json_object *o = jl_array_list_get_idx(a, i);
      if (o != NULL) {
        handler_data->filters[i] = strdup(mtev_json_object_get_string(o));
      }
    }
  }

  handler_data->restc = restc;

  strncpy(handler_data->uuid_str, check_uuid, UUID_STR_LEN);
  handler_data->uuid_str[36] = '\0';
  if(uuid_parse(handler_data->uuid_str, handler_data->uuid)) {
    mtevL(noit_error, "bad uuid received in livestream websocket_handler '%s'\n", handler_data->uuid_str);
    error = "bad uuid received in livestream subscription request";
    goto websocket_handler_error;
  }
  mtev_json_object_put(request);

  /* setup subscription to noit_livestream */
  asprintf(&handler_data->feed, "websocket_livestream/%d", mtev_atomic_inc32(&ls_counter));
  handler_data->log_stream = mtev_log_stream_new(handler_data->feed, "noit_websocket_livestream", handler_data->feed,
                                                 handler_data, NULL);

  handler_data->check = noit_check_watch(handler_data->uuid, handler_data->period);
  if(!handler_data->check) {
    error = "Cannot locate check";
    goto websocket_handler_error;
  }

  /* This check must be watched from the livestream */
  noit_check_transient_add_feed(handler_data->check, handler_data->feed);

  /* Note the check */
  noit_check_log_check(handler_data->check);

  /* kick it off, if it isn't running already */
  if(!NOIT_CHECK_LIVE(handler_data->check)) noit_check_activate(handler_data->check);

  return 0;

 websocket_handler_error:
  {
    struct mtev_json_object *e = mtev_json_object_new_object();
    mtev_json_object_object_add(e, "success", mtev_json_object_new_int(0));
    struct mtev_json_object *errors = mtev_json_object_new_array();
    struct mtev_json_object *error_o = mtev_json_object_new_object();
    mtev_json_object_object_add(error_o, "error", mtev_json_object_new_string(error));
    mtev_json_object_array_add(errors, error_o);
    mtev_json_object_object_add(e, "errors", errors);

    const char *json_error = mtev_json_object_to_json_string(e);
    mtev_http_websocket_queue_msg(restc->http_ctx,
                                  WSLAY_TEXT_FRAME | WSLAY_CONNECTION_CLOSE,
                                  (const unsigned char *)json_error, strlen(json_error));
    mtev_json_object_put(e);
  }
#endif
  return -1;
}
Example #2
0
static int
noit_console_watch_check(noit_console_closure_t ncct,
                         int argc, char **argv,
                         noit_console_state_t *state, void *closure) {
  int i, cnt;
  int adding = (int)(vpsized_int)closure;
  int period = 0;
  char xpath[1024];
  xmlXPathObjectPtr pobj = NULL;
  xmlXPathContextPtr xpath_ctxt = NULL;

  noit_conf_xml_xpath(NULL, &xpath_ctxt);
  if(argc < 1 || argc > 2) {
    nc_printf(ncct, "requires one or two arguments\n");
    return -1;
  }
  /* An alternate period */
  if(argc == 2) period = atoi(argv[1]);

  if(noit_console_mkcheck_xpath(xpath, sizeof(xpath), NULL,
                                argc ? argv[0] : NULL)) {
    nc_printf(ncct, "ERROR: could not find check '%s'\n", argv[0]);
    return -1;
  }

  pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
  if(!pobj || pobj->type != XPATH_NODESET ||
     xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
    nc_printf(ncct, "no checks found\n");
    goto out;
  }
  cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
  for(i=0; i<cnt; i++) {
    uuid_t checkid;
    noit_check_t *check;
    xmlNodePtr node;
    char *uuid_conf;

    node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
    uuid_conf = (char *)xmlGetProp(node, (xmlChar *)"uuid");
    if(!uuid_conf || uuid_parse(uuid_conf, checkid)) {
      nc_printf(ncct, "%s has invalid or missing UUID!\n",
                (char *)xmlGetNodePath(node) + strlen("/noit"));
      continue;
    }
    if(period == 0) {
      check = noit_poller_lookup(checkid);
      if(!check) continue;
      if(adding) noit_check_transient_add_feed(check, ncct->feed_path);
      else noit_check_transient_remove_feed(check, ncct->feed_path);
    }
    else {
      if(adding) {
        check = noit_check_watch(checkid, period);
        /* This check must be watched from the console */
        noit_check_transient_add_feed(check, ncct->feed_path);
        /* Note the check */
        noit_check_log_check(check);
        /* kick it off, if it isn't running already */
        if(!NOIT_CHECK_LIVE(check)) noit_check_activate(check);
      }
      else {
        check = noit_check_get_watch(checkid, period);
        if(check) noit_check_transient_remove_feed(check, ncct->feed_path);
      }
    }
  }
 out:
  if(pobj) xmlXPathFreeObject(pobj);
  return 0;
}
int
noit_livestream_handler(eventer_t e, int mask, void *closure,
                        struct timeval *now) {
  eventer_t newe;
  pthread_t tid;
  int newmask = EVENTER_READ | EVENTER_EXCEPTION;
  acceptor_closure_t *ac = closure;
  noit_livestream_closure_t *jcl = ac->service_ctx;

  if(mask & EVENTER_EXCEPTION || (jcl && jcl->wants_shutdown)) {
socket_error:
    /* Exceptions cause us to simply snip the connection */
    eventer_remove_fd(e->fd);
    e->opset->close(e->fd, &newmask, e);
    if(jcl) noit_livestream_closure_free(jcl);
    if(ac) acceptor_closure_free(ac);
    return 0;
  }

  if(!ac->service_ctx || !jcl->feed) {
    int len;
    jcl = ac->service_ctx = noit_livestream_closure_alloc();
    /* Setup logger to this channel */
    if(!jcl->period) {
      u_int32_t nperiod;
      len = e->opset->read(e->fd, &nperiod, sizeof(nperiod), &mask, e);
      if(len == -1 && errno == EAGAIN) return mask | EVENTER_EXCEPTION;
      if(len != sizeof(nperiod)) goto socket_error;
      jcl->period = ntohl(nperiod);
      if(!jcl->period) {
        noitL(noit_error, "period of 0 specified in livestream.  not allowed.\n");
        goto socket_error;
      }
    }
    while(jcl->uuid_read < 36) {
      len = e->opset->read(e->fd, jcl->uuid_str + jcl->uuid_read, 36 - jcl->uuid_read, &mask, e);
      if(len == -1 && errno == EAGAIN) return mask | EVENTER_EXCEPTION;
      if(len == 0) goto socket_error;
      jcl->uuid_read += len;
    }
    jcl->uuid_str[36] = '\0';
    if(uuid_parse(jcl->uuid_str, jcl->uuid)) {
      noitL(noit_error, "bad uuid received in livestream handler '%s'\n", jcl->uuid_str);
      goto socket_error;
    }

    jcl->feed = malloc(32);
    snprintf(jcl->feed, 32, "livestream/%d", noit_atomic_inc32(&ls_counter));
    noit_log_stream_new(jcl->feed, "noit_livestream", jcl->feed,
                        jcl, NULL);


    jcl->check = noit_check_watch(jcl->uuid, jcl->period);
    if(!jcl->check) {
      e->opset->close(e->fd, &newmask, e);
      return 0;
    }
    /* This check must be watched from the livestream */
    noit_check_transient_add_feed(jcl->check, jcl->feed);
    /* Note the check */
    noit_check_log_check(jcl->check);
    /* kick it off, if it isn't running already */
    if(!NOIT_CHECK_LIVE(jcl->check)) noit_check_activate(jcl->check);
  }

  eventer_remove_fd(e->fd);
  newe = eventer_alloc();
  memcpy(newe, e, sizeof(*e));
  if(pthread_create(&tid, NULL, noit_livestream_thread_main, newe) == 0) {
    return 0;
  }

  noit_check_transient_remove_feed(jcl->check, jcl->feed);
  noit_livestream_closure_free(jcl);
  /* Undo our dup */
  eventer_free(newe);
  /* Creating the thread failed, close it down and deschedule. */
  e->opset->close(e->fd, &newmask, e);
  return 0;
}