{"action":"created","comment":{"url":"https://api.github.com/repos/diaspora/diaspora/pulls/comments/36122440","id":36122440,"diff_hunk":"@@ -0,0 +1,243 @@\n+\n+class ConnectionTester\n+  REQUEST_OPTS = {\n+    open_timeout: 6,\n+    ssl_timeout:  6,\n+    read_timeout: 12\n+  }\n+\n+  NODEINFO_SCHEMA   = \"http://nodeinfo.diaspora.software/ns/schema/1.0\"\n+  NODEINFO_FRAGMENT = \"/.well-known/nodeinfo\"\n+\n+  class << self\n+    # Test the reachability of a server by the given HTTP/S URL.\n+    # In the first step, a DNS query is performed to check whether the\n+    # given name even resolves correctly.\n+    # The second step is to send a HTTP request and look at the returned\n+    # status code or any returned errors.\n+    # This function isn't intended to check for the availability of a\n+    # specific page, instead a GET request is sent to the root directory\n+    # of the server.\n+    # In the third step an attempt is made to determine the software version\n+    # used on the server, via the nodeinfo page.\n+    #\n+    # @param [String] server URL\n+    # @return [Result] result object containing information about the\n+    #                  server and to what point the connection was successful\n+    def check(url)\n+      url = \"http://#{url}\" unless url.include?('://')\n+      res = Result.new(host)\n+\n+      begin\n+        req_opts = request_opts_for_url(url)\n+        host = req_opts.delete(:host)\n+        ct = ConnectionTester.new(host)\n+\n+        # test DNS resolving\n+        ct.resolve(res)\n+\n+        # test HTTP request\n+        ct.request(req_opts, res)\n+\n+        # test for the diaspora* version\n+        ct.nodeinfo(res)\n+\n+      rescue Failure => e\n+        case e\n+        when AddressFailure, DNSFailure, NetFailure\n+          res.reachable = false\n+        when SSLFailure\n+          res.reachable = true\n+          res.ssl_status = false\n+        when HTTPFailure\n+          res.reachable = true\n+          res.ssl_status = true if ct.uses_ssl?\n+        when NodeInfoFailure\n+          res.diaspora_version = \"\"\n+        end\n+\n+        res.error = e\n+      end\n+\n+      res.freeze\n+    end\n+\n+    # assemble a hash of options for a Net::HTTP request.\n+    # This also includes a :host key containing the hostname of the server\n+    #\n+    # @param [String] target URL\n+    def request_opts_for_url(url)\n+      uri = URI.parse(url)\n+      raise AddressFailure, \"invalid protocol: '#{uri.scheme.upcase}'\" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)\n+      ssl = (uri.scheme == 'https') ? true : false\n+      port = ssl ? 443 : 80\n+\n+      REQUEST_OPTS.merge({\n+        host: uri.host,\n+        use_ssl: ssl,\n+        port: port,\n+        path: uri.path\n+      })\n+    rescue URI::InvalidURIError => e\n+      raise AddressFailure, e.message\n+    end\n+  end\n+\n+  def initialize(hostname)\n+    @hostname = hostname\n+  end\n+\n+  # Perform the DNS query\n+  #\n+  # @param [Result]\n+  # @return [Resolv::IPv4, Resolv::IPv6] the resolved IP address\n+  def resolve(result)\n+    res = Resolv::DNS.new\n+    addr = res.getaddress(@hostname)\n+    result.ip = addr.to_s\n+  rescue Resolv::ResolvError, Resolv::ResolvTimeout => e\n+    raise DNSFailure, \"'#{@hostname}' - #{e.message}\"\n+  ensure\n+    res.close\n+  end\n+\n+  # Perform a HTTP GET request to determine the following information\n+  #  * is the host reachable\n+  #  * is port 80/443 open\n+  #  * is the SSL certificate valid (only on HTTPS)\n+  #  * does the server return a successful HTTP status code\n+  # (can't do a HEAD request, since that's not a defined route in the app)\n+  # follow up to 3 redirects by default\n+  #\n+  # @param [Hash] request options\n+  # @param [Result]\n+  # @return [Integer] HTTP status code\n+  def request(opts, result, host=nil, max_redirects=3)\n+    host ||= @hostname\n+    raise HTTPFailure, \"too many redirects on '#{host}'\" if max_redirects == 0\n+\n+    port = opts.delete(:port)\n+    path = opts.delete(:path)\n+    @uses_ssl = opts[:use_ssl]\n+    Net::HTTP.start(host, port, nil, nil, nil, opts) do |http|\n+      start = Time.new\n+      resp  = http.get(path.present? ? path : \"/\")\n+      stop  = Time.new\n+\n+      result.status_code = Integer(resp.code)\n+      result.rt          = ((stop-start) * 1000.0).to_i # milliseconds","path":"lib/connection_tester.rb","position":128,"original_position":128,"commit_id":"05ec7e5461af3eccc5f70aa8d1a836861e1d88a9","original_commit_id":"7987d260b919c3482e9329ed0dce0c26e3e0cf66","user":{"login":"diaspora-code-review","id":13334186,"avatar_url":"https://avatars.githubusercontent.com/u/13334186?v=3","gravatar_id":"","url":"https://api.github.com/users/diaspora-code-review","html_url":"https://github.com/diaspora-code-review","followers_url":"https://api.github.com/users/diaspora-code-review/followers","following_url":"https://api.github.com/users/diaspora-code-review/following{/other_user}","gists_url":"https://api.github.com/users/diaspora-code-review/gists{/gist_id}","starred_url":"https://api.github.com/users/diaspora-code-review/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/diaspora-code-review/subscriptions","organizations_url":"https://api.github.com/users/diaspora-code-review/orgs","repos_url":"https://api.github.com/users/diaspora-code-review/repos","events_url":"https://api.github.com/users/diaspora-code-review/events{/privacy}","received_events_url":"https://api.github.com/users/diaspora-code-review/received_events","type":"User","site_admin":false},"body":"Surrounding space missing for operator `-`.","created_at":"2015-08-03T19:39:33Z","updated_at":"2015-08-03T19:39:33Z","html_url":"https://github.com/diaspora/diaspora/pull/6290#discussion_r36122440","pull_request_url":"https://api.github.com/repos/diaspora/diaspora/pulls/6290","_links":{"self":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/comments/36122440"},"html":{"href":"https://github.com/diaspora/diaspora/pull/6290#discussion_r36122440"},"pull_request":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/6290"}}},"pull_request":{"url":"https://api.github.com/repos/diaspora/diaspora/pulls/6290","id":41491181,"html_url":"https://github.com/diaspora/diaspora/pull/6290","diff_url":"https://github.com/diaspora/diaspora/pull/6290.diff","patch_url":"https://github.com/diaspora/diaspora/pull/6290.patch","issue_url":"https://api.github.com/repos/diaspora/diaspora/issues/6290","number":6290,"state":"open","locked":false,"title":"[WIP] test pods for connectivity","user":{"login":"Raven24","id":117366,"avatar_url":"https://avatars.githubusercontent.com/u/117366?v=3","gravatar_id":"","url":"https://api.github.com/users/Raven24","html_url":"https://github.com/Raven24","followers_url":"https://api.github.com/users/Raven24/followers","following_url":"https://api.github.com/users/Raven24/following{/other_user}","gists_url":"https://api.github.com/users/Raven24/gists{/gist_id}","starred_url":"https://api.github.com/users/Raven24/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Raven24/subscriptions","organizations_url":"https://api.github.com/users/Raven24/orgs","repos_url":"https://api.github.com/users/Raven24/repos","events_url":"https://api.github.com/users/Raven24/events{/privacy}","received_events_url":"https://api.github.com/users/Raven24/received_events","type":"User","site_admin":false},"body":"please start your reviews :)\r\n\r\nI'll be beginning this PR with the basic functionality to check if remote pods are reachable,\r\nthe next step will be a page in the admin backend to list the pods and show their statuses, as well as some manual interaction.\r\n\r\nMy idea was for `Pod.check_next_batch!` to run in regular intervals either via Sidetiq or a cronjob/rake task. You can also run `pod.test_connection!` on a single pod as part of the http worker before we try to send out our federation messages, or, as part of the admin backend. \r\nThere is some rudimentary \"rate control\" for pods that just recently have been determined to be unreachable - they could have an intermittent downtime. Once they are offline for more than 8 hours though, they are practically scheduled to be checked at the back of the queue.\r\n\r\nI'm not 100% sure we should completely decide whether or not to send federation messages based on these measurements, but I guess if `offline_since` exceeds a certain timespan, that should be a strong indicator.","created_at":"2015-08-03T17:40:50Z","updated_at":"2015-08-03T19:39:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"fcf80ee48397c43867225ade93a1096caa6833e3","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/diaspora/diaspora/pulls/6290/commits","review_comments_url":"https://api.github.com/repos/diaspora/diaspora/pulls/6290/comments","review_comment_url":"https://api.github.com/repos/diaspora/diaspora/pulls/comments{/number}","comments_url":"https://api.github.com/repos/diaspora/diaspora/issues/6290/comments","statuses_url":"https://api.github.com/repos/diaspora/diaspora/statuses/05ec7e5461af3eccc5f70aa8d1a836861e1d88a9","head":{"label":"Raven24:pod_test","ref":"pod_test","sha":"05ec7e5461af3eccc5f70aa8d1a836861e1d88a9","user":{"login":"Raven24","id":117366,"avatar_url":"https://avatars.githubusercontent.com/u/117366?v=3","gravatar_id":"","url":"https://api.github.com/users/Raven24","html_url":"https://github.com/Raven24","followers_url":"https://api.github.com/users/Raven24/followers","following_url":"https://api.github.com/users/Raven24/following{/other_user}","gists_url":"https://api.github.com/users/Raven24/gists{/gist_id}","starred_url":"https://api.github.com/users/Raven24/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Raven24/subscriptions","organizations_url":"https://api.github.com/users/Raven24/orgs","repos_url":"https://api.github.com/users/Raven24/repos","events_url":"https://api.github.com/users/Raven24/events{/privacy}","received_events_url":"https://api.github.com/users/Raven24/received_events","type":"User","site_admin":false},"repo":{"id":2538373,"name":"diaspora","full_name":"Raven24/diaspora","owner":{"login":"Raven24","id":117366,"avatar_url":"https://avatars.githubusercontent.com/u/117366?v=3","gravatar_id":"","url":"https://api.github.com/users/Raven24","html_url":"https://github.com/Raven24","followers_url":"https://api.github.com/users/Raven24/followers","following_url":"https://api.github.com/users/Raven24/following{/other_user}","gists_url":"https://api.github.com/users/Raven24/gists{/gist_id}","starred_url":"https://api.github.com/users/Raven24/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Raven24/subscriptions","organizations_url":"https://api.github.com/users/Raven24/orgs","repos_url":"https://api.github.com/users/Raven24/repos","events_url":"https://api.github.com/users/Raven24/events{/privacy}","received_events_url":"https://api.github.com/users/Raven24/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Raven24/diaspora","description":"Distributed and contextual social networking","fork":true,"url":"https://api.github.com/repos/Raven24/diaspora","forks_url":"https://api.github.com/repos/Raven24/diaspora/forks","keys_url":"https://api.github.com/repos/Raven24/diaspora/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Raven24/diaspora/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Raven24/diaspora/teams","hooks_url":"https://api.github.com/repos/Raven24/diaspora/hooks","issue_events_url":"https://api.github.com/repos/Raven24/diaspora/issues/events{/number}","events_url":"https://api.github.com/repos/Raven24/diaspora/events","assignees_url":"https://api.github.com/repos/Raven24/diaspora/assignees{/user}","branches_url":"https://api.github.com/repos/Raven24/diaspora/branches{/branch}","tags_url":"https://api.github.com/repos/Raven24/diaspora/tags","blobs_url":"https://api.github.com/repos/Raven24/diaspora/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Raven24/diaspora/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Raven24/diaspora/git/refs{/sha}","trees_url":"https://api.github.com/repos/Raven24/diaspora/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Raven24/diaspora/statuses/{sha}","languages_url":"https://api.github.com/repos/Raven24/diaspora/languages","stargazers_url":"https://api.github.com/repos/Raven24/diaspora/stargazers","contributors_url":"https://api.github.com/repos/Raven24/diaspora/contributors","subscribers_url":"https://api.github.com/repos/Raven24/diaspora/subscribers","subscription_url":"https://api.github.com/repos/Raven24/diaspora/subscription","commits_url":"https://api.github.com/repos/Raven24/diaspora/commits{/sha}","git_commits_url":"https://api.github.com/repos/Raven24/diaspora/git/commits{/sha}","comments_url":"https://api.github.com/repos/Raven24/diaspora/comments{/number}","issue_comment_url":"https://api.github.com/repos/Raven24/diaspora/issues/comments{/number}","contents_url":"https://api.github.com/repos/Raven24/diaspora/contents/{+path}","compare_url":"https://api.github.com/repos/Raven24/diaspora/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Raven24/diaspora/merges","archive_url":"https://api.github.com/repos/Raven24/diaspora/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Raven24/diaspora/downloads","issues_url":"https://api.github.com/repos/Raven24/diaspora/issues{/number}","pulls_url":"https://api.github.com/repos/Raven24/diaspora/pulls{/number}","milestones_url":"https://api.github.com/repos/Raven24/diaspora/milestones{/number}","notifications_url":"https://api.github.com/repos/Raven24/diaspora/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Raven24/diaspora/labels{/name}","releases_url":"https://api.github.com/repos/Raven24/diaspora/releases{/id}","created_at":"2011-10-08T14:22:50Z","updated_at":"2014-05-01T10:29:36Z","pushed_at":"2015-08-03T19:39:21Z","git_url":"git://github.com/Raven24/diaspora.git","ssh_url":"git@github.com:Raven24/diaspora.git","clone_url":"https://github.com/Raven24/diaspora.git","svn_url":"https://github.com/Raven24/diaspora","homepage":"diasporafoundation.org/get_involved","size":104752,"stargazers_count":2,"watchers_count":2,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"diaspora:develop","ref":"develop","sha":"ed4b2a2c4bb0f96ecb86c2bf4be88a0ac77ac5cb","user":{"login":"diaspora","id":293207,"avatar_url":"https://avatars.githubusercontent.com/u/293207?v=3","gravatar_id":"","url":"https://api.github.com/users/diaspora","html_url":"https://github.com/diaspora","followers_url":"https://api.github.com/users/diaspora/followers","following_url":"https://api.github.com/users/diaspora/following{/other_user}","gists_url":"https://api.github.com/users/diaspora/gists{/gist_id}","starred_url":"https://api.github.com/users/diaspora/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/diaspora/subscriptions","organizations_url":"https://api.github.com/users/diaspora/orgs","repos_url":"https://api.github.com/users/diaspora/repos","events_url":"https://api.github.com/users/diaspora/events{/privacy}","received_events_url":"https://api.github.com/users/diaspora/received_events","type":"Organization","site_admin":false},"repo":{"id":911765,"name":"diaspora","full_name":"diaspora/diaspora","owner":{"login":"diaspora","id":293207,"avatar_url":"https://avatars.githubusercontent.com/u/293207?v=3","gravatar_id":"","url":"https://api.github.com/users/diaspora","html_url":"https://github.com/diaspora","followers_url":"https://api.github.com/users/diaspora/followers","following_url":"https://api.github.com/users/diaspora/following{/other_user}","gists_url":"https://api.github.com/users/diaspora/gists{/gist_id}","starred_url":"https://api.github.com/users/diaspora/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/diaspora/subscriptions","organizations_url":"https://api.github.com/users/diaspora/orgs","repos_url":"https://api.github.com/users/diaspora/repos","events_url":"https://api.github.com/users/diaspora/events{/privacy}","received_events_url":"https://api.github.com/users/diaspora/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/diaspora/diaspora","description":"Distributed and contextual social networking","fork":false,"url":"https://api.github.com/repos/diaspora/diaspora","forks_url":"https://api.github.com/repos/diaspora/diaspora/forks","keys_url":"https://api.github.com/repos/diaspora/diaspora/keys{/key_id}","collaborators_url":"https://api.github.com/repos/diaspora/diaspora/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/diaspora/diaspora/teams","hooks_url":"https://api.github.com/repos/diaspora/diaspora/hooks","issue_events_url":"https://api.github.com/repos/diaspora/diaspora/issues/events{/number}","events_url":"https://api.github.com/repos/diaspora/diaspora/events","assignees_url":"https://api.github.com/repos/diaspora/diaspora/assignees{/user}","branches_url":"https://api.github.com/repos/diaspora/diaspora/branches{/branch}","tags_url":"https://api.github.com/repos/diaspora/diaspora/tags","blobs_url":"https://api.github.com/repos/diaspora/diaspora/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/diaspora/diaspora/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/diaspora/diaspora/git/refs{/sha}","trees_url":"https://api.github.com/repos/diaspora/diaspora/git/trees{/sha}","statuses_url":"https://api.github.com/repos/diaspora/diaspora/statuses/{sha}","languages_url":"https://api.github.com/repos/diaspora/diaspora/languages","stargazers_url":"https://api.github.com/repos/diaspora/diaspora/stargazers","contributors_url":"https://api.github.com/repos/diaspora/diaspora/contributors","subscribers_url":"https://api.github.com/repos/diaspora/diaspora/subscribers","subscription_url":"https://api.github.com/repos/diaspora/diaspora/subscription","commits_url":"https://api.github.com/repos/diaspora/diaspora/commits{/sha}","git_commits_url":"https://api.github.com/repos/diaspora/diaspora/git/commits{/sha}","comments_url":"https://api.github.com/repos/diaspora/diaspora/comments{/number}","issue_comment_url":"https://api.github.com/repos/diaspora/diaspora/issues/comments{/number}","contents_url":"https://api.github.com/repos/diaspora/diaspora/contents/{+path}","compare_url":"https://api.github.com/repos/diaspora/diaspora/compare/{base}...{head}","merges_url":"https://api.github.com/repos/diaspora/diaspora/merges","archive_url":"https://api.github.com/repos/diaspora/diaspora/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/diaspora/diaspora/downloads","issues_url":"https://api.github.com/repos/diaspora/diaspora/issues{/number}","pulls_url":"https://api.github.com/repos/diaspora/diaspora/pulls{/number}","milestones_url":"https://api.github.com/repos/diaspora/diaspora/milestones{/number}","notifications_url":"https://api.github.com/repos/diaspora/diaspora/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/diaspora/diaspora/labels{/name}","releases_url":"https://api.github.com/repos/diaspora/diaspora/releases{/id}","created_at":"2010-09-15T05:20:04Z","updated_at":"2015-08-03T12:29:42Z","pushed_at":"2015-08-03T19:39:22Z","git_url":"git://github.com/diaspora/diaspora.git","ssh_url":"git@github.com:diaspora/diaspora.git","clone_url":"https://github.com/diaspora/diaspora.git","svn_url":"https://github.com/diaspora/diaspora","homepage":"https://diasporafoundation.org/","size":254556,"stargazers_count":10184,"watchers_count":10184,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2643,"mirror_url":null,"open_issues_count":404,"forks":2643,"open_issues":404,"watchers":10184,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/6290"},"html":{"href":"https://github.com/diaspora/diaspora/pull/6290"},"issue":{"href":"https://api.github.com/repos/diaspora/diaspora/issues/6290"},"comments":{"href":"https://api.github.com/repos/diaspora/diaspora/issues/6290/comments"},"review_comments":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/6290/comments"},"review_comment":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/diaspora/diaspora/pulls/6290/commits"},"statuses":{"href":"https://api.github.com/repos/diaspora/diaspora/statuses/05ec7e5461af3eccc5f70aa8d1a836861e1d88a9"}}}}