{"action":"created","comment":{"url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/comments/52930729","id":52930729,"diff_hunk":"@@ -13,140 +13,295 @@\n # limitations under the License.\n \"\"\"Module containing classes related to Rackspace VM networking.\n \n-The SecurityGroup class provides a way of opening VM ports. The Network class\n-allows VMs to communicate via internal IPs.\n+The SecurityGroup class provides a way of opening VM ports via\n+Security group rules.\n \"\"\"\n import json\n-import os\n import threading\n \n from perfkitbenchmarker import flags\n from perfkitbenchmarker import network\n-from perfkitbenchmarker import vm_util\n+from perfkitbenchmarker import resource\n from perfkitbenchmarker.providers.rackspace import util\n from perfkitbenchmarker import providers\n \n FLAGS = flags.FLAGS\n \n+INGRESS = 'ingress'\n+EGRESS = 'egress'\n+SEC_GROUP_DIRECTIONS = frozenset([INGRESS, EGRESS])\n+\n+IPV4 = 'ipv4'\n+IPV6 = 'ipv6'\n+ETHER_TYPES = frozenset([IPV4, IPV6])\n+\n+TCP = 'tcp'\n+UDP = 'udp'\n+ICMP = 'icmp'\n+SEC_GROUP_PROTOCOLS = frozenset([TCP, UDP, ICMP])\n+\n+PORT_RANGE_MIN = '1'\n+PORT_RANGE_MAX = '65535'\n+\n+PUBLIC_NET_ID = '00000000-0000-0000-0000-000000000000'\n+SERVICE_NET_ID = '11111111-1111-1111-1111-111111111111'\n+DEFAULT_SUBNET_CIDR = '192.168.0.0/16'\n+\n SSH_PORT = 22\n \n \n-class RackspaceSecurityGroup(network.BaseFirewall):\n-    \"\"\"An object representing the Rackspace Security Group.\"\"\"\n-\n-    CLOUD = providers.RACKSPACE\n-\n-    def __init__(self):\n-        \"\"\"Initialize Rackspace security group class.\"\"\"\n-        self._lock = threading.Lock()\n-        self.firewall_names = set()\n-        self.sg_counter = 0\n-\n-    def AllowPort(self, vm, port):\n-        \"\"\"Opens a port on the firewall.\n-\n-        Args:\n-          vm: The BaseVirtualMachine object to open the port for.\n-          port: The local port to open.\n-        \"\"\"\n-        if vm.is_static or not FLAGS.use_security_group or port == SSH_PORT:\n-            return\n-        with self._lock:\n-            firewall_name = ('perfkit-firewall-%s-%d-%d' %\n-                             (FLAGS.run_uri, port, self.sg_counter))\n-            self.sg_counter += 1\n-            if firewall_name in self.firewall_names:\n-                return\n-\n-            firewall_env = dict(os.environ.copy(),\n-                                **util.GetDefaultRackspaceNeutronEnv(self))\n-\n-            firewall_cmd = [FLAGS.neutron_path]\n-            firewall_cmd.extend(['security-group-create'])\n-            firewall_cmd.append(firewall_name)\n-\n-            vm_util.IssueRetryableCommand(firewall_cmd, env=firewall_env)\n-\n-            self.firewall_names.add(firewall_name)\n-\n-            for protocol in ['tcp', 'udp']:\n-                rule_cmd = []\n-                rule_cmd.extend([FLAGS.neutron_path,\n-                                 'security-group-rule-create',\n-                                 '--direction', 'ingress',\n-                                 '--ethertype', 'IPv4',\n-                                 '--protocol', protocol,\n-                                 '--port-range-min', str(port),\n-                                 '--port-range-max', str(port)])\n-                rule_cmd.append(firewall_name)\n-\n-                vm_util.IssueRetryableCommand(rule_cmd, env=firewall_env)\n-\n-            rule_cmd = []\n-            rule_cmd.extend([FLAGS.neutron_path,\n-                             'security-group-rule-create',\n-                             '--direction', 'ingress',\n-                             '--ethertype', 'IPv4',\n-                             '--protocol', 'tcp',\n-                             '--port-range-min', str(SSH_PORT),\n-                             '--port-range-max', str(SSH_PORT)])\n-            rule_cmd.append(firewall_name)\n-\n-            vm_util.IssueRetryableCommand(rule_cmd, env=firewall_env)\n-\n-            getport_cmd = []\n-            getport_cmd.extend([FLAGS.neutron_path, 'port-list',\n-                                '--format', 'table'])\n-\n-            stdout, _ = vm_util.IssueRetryableCommand(getport_cmd,\n-                                                      env=firewall_env)\n-            attrs = stdout.split('\\n')\n-            for attr in attrs:\n-                if vm.ip_address in attr or vm.ip_address6 in attr:\n-                    port_id = [v.strip() for v in attr.split('|') if v != ''][0]\n-                    if port_id != '':\n-                        break\n-\n-            if not port_id:\n-              raise ValueError('Could not find port_id from response.')\n-\n-            updateport_cmd = []\n-            updateport_cmd.extend([FLAGS.neutron_path, 'port-update'])\n-            for firewall in self.firewall_names:\n-                updateport_cmd.extend(['--security-group', firewall])\n-            updateport_cmd.append(port_id)\n-\n-            vm_util.IssueRetryableCommand(updateport_cmd, env=firewall_env)\n-\n-    def DisallowAllPorts(self):\n-        \"\"\"Closes all ports on the firewall.\"\"\"\n-        firewall_env = dict(os.environ.copy(),\n-                            **util.GetDefaultRackspaceNeutronEnv(self))\n-\n-        for firewall in self.firewall_names:\n-            firewall_cmd = []\n-            firewall_cmd.extend([FLAGS.neutron_path,\n-                                 'security-group-show',\n-                                 '--format', 'value'])\n-            firewall_cmd.append(firewall)\n-\n-            stdout, _ = vm_util.IssueRetryableCommand(firewall_cmd,\n-                                                      env=firewall_env)\n-\n-            rules = [v for v in stdout.split('\\n') if v != ''][2:-1]\n-            for rule in rules:\n-                rule_id = str(json.loads(rule)['id'])\n-                rule_cmd = []\n-                rule_cmd.extend([FLAGS.neutron_path,\n-                                 'security-group-rule-delete'])\n-                rule_cmd.append(rule_id)\n-\n-                vm_util.IssueRetryableCommand(rule_cmd, env=firewall_env)\n-\n-            firewall_cmd = [FLAGS.neutron_path]\n-            firewall_cmd.extend(['security-group-delete'])\n-            firewall_cmd.append(firewall)\n-\n-            vm_util.IssueRetryableCommand(firewall_cmd, env=firewall_env)\n-\n-            self.firewall_names.remove(firewall)\n+class RackspaceSecurityGroup(resource.BaseResource):\n+  \"\"\"An object representing a Rackspace Security Group.\"\"\"\n+\n+  def __init__(self, name):\n+    super(RackspaceSecurityGroup, self).__init__()\n+    self.name = name\n+    self.id = None\n+\n+  def _Create(self):\n+    cmd = util.RackCLICommand(self, 'networks', 'security-group', 'create')\n+    cmd.flags['name'] = self.name\n+    stdout, stderr, _ = cmd.Issue()\n+    resp = json.loads(stdout)\n+    self.id = resp['ID']\n+\n+  def _Delete(self):\n+    if self.id is None:\n+      return\n+    cmd = util.RackCLICommand(self, 'networks', 'security-group', 'delete')\n+    cmd.flags['id'] = self.id\n+    cmd.Issue()\n+\n+  def _Exists(self):\n+    if self.id is None:\n+      return False\n+    cmd = util.RackCLICommand(self, 'networks', 'security-group', 'get')\n+    cmd.flags['id'] = self.id\n+    stdout, stderr, _ = cmd.Issue()\n+    return not stderr\n+\n+\n+class RackspaceSecurityGroupRule(resource.BaseResource):\n+  \"\"\"An object representing a Security Group Rule.\"\"\"\n+\n+  def __init__(self, sec_group_rule_name, sec_group_id, direction=None,\n+               ip_ver=None, protocol=None, port_range_min=None,\n+               port_range_max=None, source_cidr=None):\n+    super(RackspaceSecurityGroupRule, self).__init__()\n+    self.id = None\n+    self.name = sec_group_rule_name\n+    self.sec_group_id = sec_group_id\n+    self.direction = direction if direction in SEC_GROUP_DIRECTIONS else INGRESS","path":"perfkitbenchmarker/providers/rackspace/rackspace_network.py","position":205,"original_position":205,"commit_id":"f985590f8fbd8cac38c00ad8b028d0c3cbcbb4dd","original_commit_id":"9f657ac766212158b23c9badbe5a4c578a681ed3","user":{"login":"noahl","id":16882,"avatar_url":"https://avatars.githubusercontent.com/u/16882?v=3","gravatar_id":"","url":"https://api.github.com/users/noahl","html_url":"https://github.com/noahl","followers_url":"https://api.github.com/users/noahl/followers","following_url":"https://api.github.com/users/noahl/following{/other_user}","gists_url":"https://api.github.com/users/noahl/gists{/gist_id}","starred_url":"https://api.github.com/users/noahl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/noahl/subscriptions","organizations_url":"https://api.github.com/users/noahl/orgs","repos_url":"https://api.github.com/users/noahl/repos","events_url":"https://api.github.com/users/noahl/events{/privacy}","received_events_url":"https://api.github.com/users/noahl/received_events","type":"User","site_admin":false},"body":"With this, as well as `ip_ver`, `protocol`, `port_range_min`, and `port_range_max`, I'd recommend throwing an error if the user provides an argument other than `None` or one of the allowed options. Right now this code will silently ignore bad input.\r\n\r\nHow to do that is up to you, although I think it might be easiest to specify the defaults as method argument defaults in the `def __init__(...)` line and then in the body just check that the input is valid. (I know this will make the method longer. Sorry.)","created_at":"2016-02-15T18:29:51Z","updated_at":"2016-02-15T18:29:51Z","html_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844#discussion_r52930729","pull_request_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844","_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/comments/52930729"},"html":{"href":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844#discussion_r52930729"},"pull_request":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844"}}},"pull_request":{"url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844","id":59240991,"html_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844","diff_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844.diff","patch_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844.patch","issue_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/844","number":844,"state":"open","locked":false,"title":"rackspace: Migrate provider implementation to use official CLI.","user":{"login":"meteorfox","id":232300,"avatar_url":"https://avatars.githubusercontent.com/u/232300?v=3","gravatar_id":"","url":"https://api.github.com/users/meteorfox","html_url":"https://github.com/meteorfox","followers_url":"https://api.github.com/users/meteorfox/followers","following_url":"https://api.github.com/users/meteorfox/following{/other_user}","gists_url":"https://api.github.com/users/meteorfox/gists{/gist_id}","starred_url":"https://api.github.com/users/meteorfox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meteorfox/subscriptions","organizations_url":"https://api.github.com/users/meteorfox/orgs","repos_url":"https://api.github.com/users/meteorfox/repos","events_url":"https://api.github.com/users/meteorfox/events{/privacy}","received_events_url":"https://api.github.com/users/meteorfox/received_events","type":"User","site_admin":false},"body":"Migrate provider implementation to use official Rackspace CLI and introduces experimental support for security groups, and isolated networks.\r\n\r\nUses new Disk types:\r\n  - `cbs-sata` for a HDD, or SATA, Cloud Block Storage Volume\r\n  - `cbs-ssd` for a SSD Cloud Block Storage Volume\r\n  - `boot` creates data disk mount point  as path within the boot disk.\r\n  - `local` uses, if any, provided local disks that come with the machine type\r\n\r\nResolves Issue #533\r\n\r\nSigned-off-by: Carlos L. Torres <carlos.torres@rackspace.com>","created_at":"2016-02-12T22:34:20Z","updated_at":"2016-02-15T18:29:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"701498599e1a4b88b7ca6cdd698b9f8787877821","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844/commits","review_comments_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844/comments","review_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/844/comments","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/statuses/f985590f8fbd8cac38c00ad8b028d0c3cbcbb4dd","head":{"label":"meteorfox:rackspace-rack-cli","ref":"rackspace-rack-cli","sha":"f985590f8fbd8cac38c00ad8b028d0c3cbcbb4dd","user":{"login":"meteorfox","id":232300,"avatar_url":"https://avatars.githubusercontent.com/u/232300?v=3","gravatar_id":"","url":"https://api.github.com/users/meteorfox","html_url":"https://github.com/meteorfox","followers_url":"https://api.github.com/users/meteorfox/followers","following_url":"https://api.github.com/users/meteorfox/following{/other_user}","gists_url":"https://api.github.com/users/meteorfox/gists{/gist_id}","starred_url":"https://api.github.com/users/meteorfox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meteorfox/subscriptions","organizations_url":"https://api.github.com/users/meteorfox/orgs","repos_url":"https://api.github.com/users/meteorfox/repos","events_url":"https://api.github.com/users/meteorfox/events{/privacy}","received_events_url":"https://api.github.com/users/meteorfox/received_events","type":"User","site_admin":false},"repo":{"id":31044872,"name":"PerfKitBenchmarker","full_name":"meteorfox/PerfKitBenchmarker","owner":{"login":"meteorfox","id":232300,"avatar_url":"https://avatars.githubusercontent.com/u/232300?v=3","gravatar_id":"","url":"https://api.github.com/users/meteorfox","html_url":"https://github.com/meteorfox","followers_url":"https://api.github.com/users/meteorfox/followers","following_url":"https://api.github.com/users/meteorfox/following{/other_user}","gists_url":"https://api.github.com/users/meteorfox/gists{/gist_id}","starred_url":"https://api.github.com/users/meteorfox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meteorfox/subscriptions","organizations_url":"https://api.github.com/users/meteorfox/orgs","repos_url":"https://api.github.com/users/meteorfox/repos","events_url":"https://api.github.com/users/meteorfox/events{/privacy}","received_events_url":"https://api.github.com/users/meteorfox/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/meteorfox/PerfKitBenchmarker","description":"Perfkit Benchmarker contains set of benchmarks to measure and compare cloud offerings. The benchmarks are not tuned (i.e. use defaults) to reflect what most users will see. PerfKit Benchmarker is licensed under the Apache 2 license terms. Please make sure to read, understand and agree to the terms of the LICENSE and CONTRIBUTING files before proceeding.","fork":true,"url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker","forks_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/forks","keys_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/teams","hooks_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/hooks","issue_events_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/issues/events{/number}","events_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/events","assignees_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/assignees{/user}","branches_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/branches{/branch}","tags_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/tags","blobs_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/git/refs{/sha}","trees_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/statuses/{sha}","languages_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/languages","stargazers_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/stargazers","contributors_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/contributors","subscribers_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/subscribers","subscription_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/subscription","commits_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/commits{/sha}","git_commits_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/git/commits{/sha}","comments_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/comments{/number}","issue_comment_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/issues/comments{/number}","contents_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/contents/{+path}","compare_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/merges","archive_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/downloads","issues_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/issues{/number}","pulls_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/pulls{/number}","milestones_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/milestones{/number}","notifications_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/labels{/name}","releases_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/releases{/id}","deployments_url":"https://api.github.com/repos/meteorfox/PerfKitBenchmarker/deployments","created_at":"2015-02-20T00:39:49Z","updated_at":"2016-01-15T16:06:24Z","pushed_at":"2016-02-15T18:29:14Z","git_url":"git://github.com/meteorfox/PerfKitBenchmarker.git","ssh_url":"git@github.com:meteorfox/PerfKitBenchmarker.git","clone_url":"https://github.com/meteorfox/PerfKitBenchmarker.git","svn_url":"https://github.com/meteorfox/PerfKitBenchmarker","homepage":"","size":4096,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"GoogleCloudPlatform:master","ref":"master","sha":"47661f69cdbd923da71c3118632c95fbf2ccb35c","user":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=3","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"repo":{"id":21950910,"name":"PerfKitBenchmarker","full_name":"GoogleCloudPlatform/PerfKitBenchmarker","owner":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=3","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker","description":"Perfkit Benchmarker contains set of benchmarks to measure and compare cloud offerings. The benchmarks use defaults to reflect what most users will see. PerfKit Benchmarker is licensed under the Apache 2 license terms. Please make sure to read, understand and agree to the terms of the LICENSE and CONTRIBUTING files before proceeding.","fork":false,"url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker","forks_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/forks","keys_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/teams","hooks_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/hooks","issue_events_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/events{/number}","events_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/events","assignees_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/assignees{/user}","branches_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/branches{/branch}","tags_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/tags","blobs_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/git/refs{/sha}","trees_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/statuses/{sha}","languages_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/languages","stargazers_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/stargazers","contributors_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/contributors","subscribers_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/subscribers","subscription_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/subscription","commits_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/commits{/sha}","git_commits_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/git/commits{/sha}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/comments{/number}","issue_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/comments{/number}","contents_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/contents/{+path}","compare_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/merges","archive_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/downloads","issues_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues{/number}","pulls_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls{/number}","milestones_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/milestones{/number}","notifications_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/labels{/name}","releases_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/releases{/id}","deployments_url":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/deployments","created_at":"2014-07-17T17:23:26Z","updated_at":"2016-02-15T04:14:47Z","pushed_at":"2016-02-15T18:29:14Z","git_url":"git://github.com/GoogleCloudPlatform/PerfKitBenchmarker.git","ssh_url":"git@github.com:GoogleCloudPlatform/PerfKitBenchmarker.git","clone_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker.git","svn_url":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker","homepage":"http://googlecloudplatform.github.io/PerfKitBenchmarker/","size":84932,"stargazers_count":590,"watchers_count":590,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":114,"mirror_url":null,"open_issues_count":112,"forks":114,"open_issues":112,"watchers":590,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844"},"html":{"href":"https://github.com/GoogleCloudPlatform/PerfKitBenchmarker/pull/844"},"issue":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/844"},"comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/issues/844/comments"},"review_comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844/comments"},"review_comment":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/pulls/844/commits"},"statuses":{"href":"https://api.github.com/repos/GoogleCloudPlatform/PerfKitBenchmarker/statuses/f985590f8fbd8cac38c00ad8b028d0c3cbcbb4dd"}}}}