{"action":"created","comment":{"url":"https://api.github.com/repos/chef/chef-server/pulls/comments/42287768","id":42287768,"diff_hunk":"@@ -0,0 +1,473 @@\n+% -*- erlang-indent-level: 4;indent-tabs-mode: nil; fill-column: 92 -*-\n+%% ex: ts=4 sw=4 et\n+%%% @author Dave Parfitt <dparfitt@chef.io>\n+%%% @doc\n+%%% monitor RabbitMQ length of analytics queues\n+%%% @end\n+%% Copyright 2011-2015 Chef Software, Inc. All Rights Reserved.\n+%%\n+%% This file is provided to you under the Apache License,\n+%% Version 2.0 (the \"License\"); you may not use this file\n+%% except in compliance with the License.  You may obtain\n+%% a copy of the License at\n+%%\n+%%   http://www.apache.org/licenses/LICENSE-2.0\n+%%\n+%% Unless required by applicable law or agreed to in writing,\n+%% software distributed under the License is distributed on an\n+%% \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+%% KIND, either express or implied.  See the License for the\n+%% specific language governing permissions and limitations\n+%% under the License.\n+%%\n+\n+\n+%% This gen_server periodically checks the RabbitMQ management console for\n+%% max_length and current queue length values.\n+%%\n+%% 0) If a max_length policy is not set on the exchange, do nothing and recheck\n+%%    at the next interval.\n+%% 1) If a max_length policy IS set on the exchange but a queue is not\n+%%    bound to the /analytics exchange, do nothing and recheck at the\n+%%    next interval.\n+%% 2) If a max_length policy IS set on the exchange, store the value and\n+%%    proceed to check the current queue length. Store max_length and\n+%%    current length values.\n+%% 2a) If current length < max_length, and the queue is < 80% full,\n+%%     set queue_at_capacity = false, continue\n+%% 2b) If current length < max_length, and the queue is >= 80% && < 100% full\n+%%     log a warning message stating the current percentage of capacity,\n+%%     set queue_at_capacity, continue\n+%% 2b) If current length = max_length, set queue_at_capacity = true,\n+%%     log a warning message stating the current percentage of capacity\n+\n+\n+%% clients of chef_wm_actions_queue_monitoring use 2 API calls:\n+%% is_queue_at_capacity/0 and message_dropped/0. When attempting to send\n+%% message to RabbitMQ, a client can check is_queue_at_capacity/0 and\n+%% optionally not send a message if true (\"dropping a message\"). Clients\n+%% that drop messages should report that a message has been dropped\n+%% to this gen_server via message_dropped/0. Note, message_dropped/0\n+%% is a cast, while is_queue_at_capacity/0 is a call. If calls to\n+%% is_queue_at_capacity/0 take \"too long\", a timeout can be set via\n+%% the `rabbitmq_queue_length_timeout_millis` config setting. This\n+%% setting will force is_queue_at_capacity/0 to return `true` upon\n+%% timeout, even if it's not necessarily the queue that is timing\n+%% out.\n+\n+\n+-module(chef_wm_actions_queue_monitoring).\n+\n+-ifdef(TEST).\n+-compile(export_all).\n+-endif.\n+\n+-export([start_link/0]).\n+-export([init/1,\n+         handle_call/3,\n+         handle_cast/2,\n+         handle_info/2,\n+         terminate/2,\n+         code_change/3,\n+         stop/0,\n+         status/0,\n+         is_queue_at_capacity/0,\n+         message_dropped/0,\n+         create_pool/0,\n+         delete_pool/0,\n+         get_pool_configs/0,\n+         override_queue_at_capacity/1,\n+         sync_check_current_state/0,\n+         start_timer/0,\n+         stop_timer/0\n+\n+        ]).\n+\n+-behaviour(gen_server).\n+-define(SERVER, ?MODULE).\n+\n+\n+-record(queue_monitor_state, {\n+                % has the max_length of the queue been reached?\n+                queue_at_capacity = false,\n+                % timer to check max and current queue length\n+                timer = undefined,\n+                % maximum queue length set by rabbitmq policy\n+                % mostly static, but a user *can* change the value\n+                % via rabbitmqctl\n+                max_length = 0,\n+                % last recorded length of the analytics queue\n+                last_recorded_length = 0,\n+                % number of messages that have NOT been sent to the analytics\n+                % queue due to queue_at_capacity = true.\n+                % This metric is manually reported by users of the queue\n+                % monitor\n+                dropped_since_last_check = 0,\n+                % monotonically increasing number of messages that have NOT\n+                % been sent to the analytics queue due to queue_at_capacity =\n+                % true. This number is never reset.\n+                total_dropped = 0,\n+                % The async worker pid responsible for checking max length and\n+                % current length of the queue. There can ONLY be ONE worker\n+                % process.\n+                worker_process = undefined,\n+                % if sync_check_current_state/0 is called, this is the Pid of the\n+                % calling process. This is recorded because checking the queue\n+                % length and max is async via the worker_process, and the\n+                % reply is sent back to this Pid via gen_server:reply()\n+                sync_response_process = undefined\n+               }).\n+\n+% exchange is /analytics, so encode the / as %2F\n+% NOTE: oc_httpc client is configured to prepend /api\n+-define(MAX_LENGTH_PATH, \"/policies/%2Fanalytics/max_length\").\n+-define(QUEUE_LENGTH_PATH, \"/queues/%2Fanalytics\").\n+-define(LOG_THRESHOLD, 0.8).\n+-define(POOLNAME, rabbitmq_management_service).\n+\n+\n+start_link() ->\n+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).\n+\n+-spec status() -> [{atom(), _}].\n+status() ->\n+    gen_server:call(?SERVER, status).\n+\n+% is current queue length >= max queue length?\n+% Note, this simply checks the gen_server state, and does NOT\n+% ping the RabbitMQ management console.\n+% Pinging the management console is handled by an internal timer\n+% so we never block waiting for an HTTP response.\n+is_queue_at_capacity() ->\n+    Timeout = envy:get(oc_chef_wm, rabbitmq_queue_length_timeout_millis, 5000, non_neg_integer),\n+    try\n+        gen_server:call(?SERVER, is_queue_at_capacity, Timeout)\n+    catch Error:Reason ->\n+        lager:warning(\"Queue monitor timeout ~p ~p \", [Error, Reason]),\n+        true\n+    end.\n+\n+% log that a message has been dropped (hasn't been sent to rabbitmq)\n+% This is a cast so we don't wait for an `ok` response\n+% back to the callee, which a call would have returned as well.\n+message_dropped() ->\n+    gen_server:cast(?SERVER, message_dropped).\n+\n+\n+-spec stop() -> ok.\n+stop() ->\n+    gen_server:call(?SERVER, stop).\n+\n+\n+%% Support functions --------------------------------------------\n+% Synchronously update state by pinging the RabbitMQ Management Console\n+sync_check_current_state() ->\n+    gen_server:call(?SERVER, sync_check_current_state).\n+\n+% manually toggle the at capacity flag\n+override_queue_at_capacity(AtCapacity) ->\n+    gen_server:call(?SERVER, {override_queue_at_capacity, AtCapacity}).\n+\n+start_timer() ->\n+    gen_server:call(?SERVER, start_timer).\n+\n+stop_timer() ->\n+    gen_server:call(?SERVER, stop_timer).\n+\n+\n+%% oc_httpc pool functions --------------------------------------------\n+create_pool() ->\n+    Pools = get_pool_configs(),\n+    [oc_httpc:add_pool(PoolNameAtom, Config) || {PoolNameAtom, Config} <- Pools, Config /= []],\n+    ok.\n+\n+delete_pool() ->\n+    Pools = get_pool_configs(),\n+    [ok = oc_httpc:delete_pool(PoolNameAtom) || {PoolNameAtom, _Config} <- Pools],\n+    ok.\n+\n+get_pool_configs() ->\n+    Config = envy:get(oc_chef_wm, ?POOLNAME, [], any),\n+    [{?POOLNAME, Config}].\n+\n+%%-------------------------------------------------------------\n+\n+init([]) ->\n+    % used to catch worker msgs\n+    process_flag(trap_exit, true),\n+    TRef = start_update_timer(),\n+    {ok, #queue_monitor_state{timer=TRef, worker_process = undefined}}.\n+\n+\n+%%-------------------------------------------------------------\n+%% CALLS\n+handle_call(is_queue_at_capacity, _From, #queue_monitor_state{queue_at_capacity =\n+                                                QueueAtCapacity} = State) ->\n+    {reply, QueueAtCapacity, State};\n+\n+handle_call({override_queue_at_capacity, AtCapacity}, _From, State) ->\n+    lager:info(\"Manually setting Queue Monitor queue_at_capacity ~p\", [AtCapacity]),\n+    {reply, ok, State#queue_monitor_state{queue_at_capacity = AtCapacity}};\n+handle_call(status, _From, State) ->\n+    % return a term to be converted to JSON\n+    Stats = [{queue_at_capacity,State#queue_monitor_state.queue_at_capacity},\n+             {dropped_since_last_check, State#queue_monitor_state.dropped_since_last_check},\n+             {max_length, State#queue_monitor_state.max_length},\n+             {last_recorded_length, State#queue_monitor_state.last_recorded_length},\n+             {total_dropped, State#queue_monitor_state.total_dropped}],\n+    {reply, Stats, State};\n+handle_call(sync_check_current_state, From, #queue_monitor_state{sync_response_process = undefined} = State) ->\n+     self() ! status_ping,\n+     {noreply, State#queue_monitor_state{sync_response_process = From}};","path":"src/oc_erchef/apps/oc_chef_wm/src/chef_wm_actions_queue_monitoring.erl","position":221,"original_position":221,"commit_id":"b19fe06e73de898f14610f543b847ca9c85e46ed","original_commit_id":"510c46d1a424a9d15e65d4628a3c9851e16959ec","user":{"login":"metadave","id":58244,"avatar_url":"https://avatars.githubusercontent.com/u/58244?v=3","gravatar_id":"","url":"https://api.github.com/users/metadave","html_url":"https://github.com/metadave","followers_url":"https://api.github.com/users/metadave/followers","following_url":"https://api.github.com/users/metadave/following{/other_user}","gists_url":"https://api.github.com/users/metadave/gists{/gist_id}","starred_url":"https://api.github.com/users/metadave/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metadave/subscriptions","organizations_url":"https://api.github.com/users/metadave/orgs","repos_url":"https://api.github.com/users/metadave/repos","events_url":"https://api.github.com/users/metadave/events{/privacy}","received_events_url":"https://api.github.com/users/metadave/received_events","type":"User","site_admin":false},"body":"Yes, but it's only used for testing.","created_at":"2015-10-16T20:40:53Z","updated_at":"2015-10-16T20:40:53Z","html_url":"https://github.com/chef/chef-server/pull/570#discussion_r42287768","pull_request_url":"https://api.github.com/repos/chef/chef-server/pulls/570","_links":{"self":{"href":"https://api.github.com/repos/chef/chef-server/pulls/comments/42287768"},"html":{"href":"https://github.com/chef/chef-server/pull/570#discussion_r42287768"},"pull_request":{"href":"https://api.github.com/repos/chef/chef-server/pulls/570"}}},"pull_request":{"url":"https://api.github.com/repos/chef/chef-server/pulls/570","id":47713060,"html_url":"https://github.com/chef/chef-server/pull/570","diff_url":"https://github.com/chef/chef-server/pull/570.diff","patch_url":"https://github.com/chef/chef-server/pull/570.patch","issue_url":"https://api.github.com/repos/chef/chef-server/issues/570","number":570,"state":"open","locked":false,"title":"rabbitmq queue monitor + queue stability enhancements","user":{"login":"metadave","id":58244,"avatar_url":"https://avatars.githubusercontent.com/u/58244?v=3","gravatar_id":"","url":"https://api.github.com/users/metadave","html_url":"https://github.com/metadave","followers_url":"https://api.github.com/users/metadave/followers","following_url":"https://api.github.com/users/metadave/following{/other_user}","gists_url":"https://api.github.com/users/metadave/gists{/gist_id}","starred_url":"https://api.github.com/users/metadave/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metadave/subscriptions","organizations_url":"https://api.github.com/users/metadave/orgs","repos_url":"https://api.github.com/users/metadave/repos","events_url":"https://api.github.com/users/metadave/events{/privacy}","received_events_url":"https://api.github.com/users/metadave/received_events","type":"User","site_admin":false},"body":"This PR adds several enhancements for RabbitMQ stability.\r\n\r\n1. a `max-length` policy is set on the `/analytics` exchange\r\n2. The RabbitMQ Management console is enabled by default with SSL on port `15672`. A `rabbitmgmt` user is created to access this console.\r\n3. A Queue Length monitoring gen\\_server (`chef_wm_actions_queue_monitoring.erl`). This gen\\_server periodically connects to the management endpoint to check max-length and current length of the analytics queue. If the queue is at > 80% capacity, a warning is issued via lager. If the queue reaches 100% capacity, toggle a \"queue at capacity\" flag until messages have been read/purged from the queue. Code that publishes messages to RabbitMQ can check the state of the \"queue at capacity\" flag before enqueuing. \r\n4. Queue length statistics are reporting via the `_status` endpoint.\r\n\r\nMore details are available in `chef_wm_actions_queue_monitoring.erl`.\r\n\r\n---\r\n### Notes\r\n\r\n* This PR adds the PropER testing tool, which is ***GPL V3***. HOWEVER, this is a ***test-only dependency***.\r\n\r\n* SSL for the RabbitMQ Management has been configured to only accept `tlsv1.2` and `tlsv1.1` (POODLE, BEAST) via:\r\n\r\n\r\n\t\t{ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}\r\n\r\n---\r\n\r\n### TODO: \r\n\r\n- [x] tests in `chef_wm_status_tests.erl`\r\n- [ ] integration tests/local rabbitmq setup\r\n\r\n\t","created_at":"2015-10-14T20:50:19Z","updated_at":"2015-10-16T20:40:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"432c5b4bb99aba506ef329763f519597e7a79688","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/chef/chef-server/pulls/570/commits","review_comments_url":"https://api.github.com/repos/chef/chef-server/pulls/570/comments","review_comment_url":"https://api.github.com/repos/chef/chef-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/chef/chef-server/issues/570/comments","statuses_url":"https://api.github.com/repos/chef/chef-server/statuses/b19fe06e73de898f14610f543b847ca9c85e46ed","head":{"label":"chef:dp_rabbit_monitoring","ref":"dp_rabbit_monitoring","sha":"b19fe06e73de898f14610f543b847ca9c85e46ed","user":{"login":"chef","id":29740,"avatar_url":"https://avatars.githubusercontent.com/u/29740?v=3","gravatar_id":"","url":"https://api.github.com/users/chef","html_url":"https://github.com/chef","followers_url":"https://api.github.com/users/chef/followers","following_url":"https://api.github.com/users/chef/following{/other_user}","gists_url":"https://api.github.com/users/chef/gists{/gist_id}","starred_url":"https://api.github.com/users/chef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chef/subscriptions","organizations_url":"https://api.github.com/users/chef/orgs","repos_url":"https://api.github.com/users/chef/repos","events_url":"https://api.github.com/users/chef/events{/privacy}","received_events_url":"https://api.github.com/users/chef/received_events","type":"Organization","site_admin":false},"repo":{"id":24732433,"name":"chef-server","full_name":"chef/chef-server","owner":{"login":"chef","id":29740,"avatar_url":"https://avatars.githubusercontent.com/u/29740?v=3","gravatar_id":"","url":"https://api.github.com/users/chef","html_url":"https://github.com/chef","followers_url":"https://api.github.com/users/chef/followers","following_url":"https://api.github.com/users/chef/following{/other_user}","gists_url":"https://api.github.com/users/chef/gists{/gist_id}","starred_url":"https://api.github.com/users/chef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chef/subscriptions","organizations_url":"https://api.github.com/users/chef/orgs","repos_url":"https://api.github.com/users/chef/repos","events_url":"https://api.github.com/users/chef/events{/privacy}","received_events_url":"https://api.github.com/users/chef/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/chef/chef-server","description":"The Chef Server ","fork":false,"url":"https://api.github.com/repos/chef/chef-server","forks_url":"https://api.github.com/repos/chef/chef-server/forks","keys_url":"https://api.github.com/repos/chef/chef-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/chef/chef-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/chef/chef-server/teams","hooks_url":"https://api.github.com/repos/chef/chef-server/hooks","issue_events_url":"https://api.github.com/repos/chef/chef-server/issues/events{/number}","events_url":"https://api.github.com/repos/chef/chef-server/events","assignees_url":"https://api.github.com/repos/chef/chef-server/assignees{/user}","branches_url":"https://api.github.com/repos/chef/chef-server/branches{/branch}","tags_url":"https://api.github.com/repos/chef/chef-server/tags","blobs_url":"https://api.github.com/repos/chef/chef-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/chef/chef-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/chef/chef-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/chef/chef-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/chef/chef-server/statuses/{sha}","languages_url":"https://api.github.com/repos/chef/chef-server/languages","stargazers_url":"https://api.github.com/repos/chef/chef-server/stargazers","contributors_url":"https://api.github.com/repos/chef/chef-server/contributors","subscribers_url":"https://api.github.com/repos/chef/chef-server/subscribers","subscription_url":"https://api.github.com/repos/chef/chef-server/subscription","commits_url":"https://api.github.com/repos/chef/chef-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/chef/chef-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/chef/chef-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/chef/chef-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/chef/chef-server/contents/{+path}","compare_url":"https://api.github.com/repos/chef/chef-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/chef/chef-server/merges","archive_url":"https://api.github.com/repos/chef/chef-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/chef/chef-server/downloads","issues_url":"https://api.github.com/repos/chef/chef-server/issues{/number}","pulls_url":"https://api.github.com/repos/chef/chef-server/pulls{/number}","milestones_url":"https://api.github.com/repos/chef/chef-server/milestones{/number}","notifications_url":"https://api.github.com/repos/chef/chef-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/chef/chef-server/labels{/name}","releases_url":"https://api.github.com/repos/chef/chef-server/releases{/id}","created_at":"2014-10-02T19:04:18Z","updated_at":"2015-10-16T15:41:58Z","pushed_at":"2015-10-16T20:19:49Z","git_url":"git://github.com/chef/chef-server.git","ssh_url":"git@github.com:chef/chef-server.git","clone_url":"https://github.com/chef/chef-server.git","svn_url":"https://github.com/chef/chef-server","homepage":"https://www.chef.io/chef/","size":20585,"stargazers_count":50,"watchers_count":50,"language":"Erlang","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":34,"mirror_url":null,"open_issues_count":204,"forks":34,"open_issues":204,"watchers":50,"default_branch":"master"}},"base":{"label":"chef:master","ref":"master","sha":"a727f9e10a585ad59f4a42b19860bc696019a5c0","user":{"login":"chef","id":29740,"avatar_url":"https://avatars.githubusercontent.com/u/29740?v=3","gravatar_id":"","url":"https://api.github.com/users/chef","html_url":"https://github.com/chef","followers_url":"https://api.github.com/users/chef/followers","following_url":"https://api.github.com/users/chef/following{/other_user}","gists_url":"https://api.github.com/users/chef/gists{/gist_id}","starred_url":"https://api.github.com/users/chef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chef/subscriptions","organizations_url":"https://api.github.com/users/chef/orgs","repos_url":"https://api.github.com/users/chef/repos","events_url":"https://api.github.com/users/chef/events{/privacy}","received_events_url":"https://api.github.com/users/chef/received_events","type":"Organization","site_admin":false},"repo":{"id":24732433,"name":"chef-server","full_name":"chef/chef-server","owner":{"login":"chef","id":29740,"avatar_url":"https://avatars.githubusercontent.com/u/29740?v=3","gravatar_id":"","url":"https://api.github.com/users/chef","html_url":"https://github.com/chef","followers_url":"https://api.github.com/users/chef/followers","following_url":"https://api.github.com/users/chef/following{/other_user}","gists_url":"https://api.github.com/users/chef/gists{/gist_id}","starred_url":"https://api.github.com/users/chef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chef/subscriptions","organizations_url":"https://api.github.com/users/chef/orgs","repos_url":"https://api.github.com/users/chef/repos","events_url":"https://api.github.com/users/chef/events{/privacy}","received_events_url":"https://api.github.com/users/chef/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/chef/chef-server","description":"The Chef Server ","fork":false,"url":"https://api.github.com/repos/chef/chef-server","forks_url":"https://api.github.com/repos/chef/chef-server/forks","keys_url":"https://api.github.com/repos/chef/chef-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/chef/chef-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/chef/chef-server/teams","hooks_url":"https://api.github.com/repos/chef/chef-server/hooks","issue_events_url":"https://api.github.com/repos/chef/chef-server/issues/events{/number}","events_url":"https://api.github.com/repos/chef/chef-server/events","assignees_url":"https://api.github.com/repos/chef/chef-server/assignees{/user}","branches_url":"https://api.github.com/repos/chef/chef-server/branches{/branch}","tags_url":"https://api.github.com/repos/chef/chef-server/tags","blobs_url":"https://api.github.com/repos/chef/chef-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/chef/chef-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/chef/chef-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/chef/chef-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/chef/chef-server/statuses/{sha}","languages_url":"https://api.github.com/repos/chef/chef-server/languages","stargazers_url":"https://api.github.com/repos/chef/chef-server/stargazers","contributors_url":"https://api.github.com/repos/chef/chef-server/contributors","subscribers_url":"https://api.github.com/repos/chef/chef-server/subscribers","subscription_url":"https://api.github.com/repos/chef/chef-server/subscription","commits_url":"https://api.github.com/repos/chef/chef-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/chef/chef-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/chef/chef-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/chef/chef-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/chef/chef-server/contents/{+path}","compare_url":"https://api.github.com/repos/chef/chef-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/chef/chef-server/merges","archive_url":"https://api.github.com/repos/chef/chef-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/chef/chef-server/downloads","issues_url":"https://api.github.com/repos/chef/chef-server/issues{/number}","pulls_url":"https://api.github.com/repos/chef/chef-server/pulls{/number}","milestones_url":"https://api.github.com/repos/chef/chef-server/milestones{/number}","notifications_url":"https://api.github.com/repos/chef/chef-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/chef/chef-server/labels{/name}","releases_url":"https://api.github.com/repos/chef/chef-server/releases{/id}","created_at":"2014-10-02T19:04:18Z","updated_at":"2015-10-16T15:41:58Z","pushed_at":"2015-10-16T20:19:49Z","git_url":"git://github.com/chef/chef-server.git","ssh_url":"git@github.com:chef/chef-server.git","clone_url":"https://github.com/chef/chef-server.git","svn_url":"https://github.com/chef/chef-server","homepage":"https://www.chef.io/chef/","size":20585,"stargazers_count":50,"watchers_count":50,"language":"Erlang","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":34,"mirror_url":null,"open_issues_count":204,"forks":34,"open_issues":204,"watchers":50,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/chef/chef-server/pulls/570"},"html":{"href":"https://github.com/chef/chef-server/pull/570"},"issue":{"href":"https://api.github.com/repos/chef/chef-server/issues/570"},"comments":{"href":"https://api.github.com/repos/chef/chef-server/issues/570/comments"},"review_comments":{"href":"https://api.github.com/repos/chef/chef-server/pulls/570/comments"},"review_comment":{"href":"https://api.github.com/repos/chef/chef-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/chef/chef-server/pulls/570/commits"},"statuses":{"href":"https://api.github.com/repos/chef/chef-server/statuses/b19fe06e73de898f14610f543b847ca9c85e46ed"}}}}