{"action":"created","comment":{"url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments/37536648","id":37536648,"diff_hunk":"@@ -249,3 +333,202 @@ def test_complex_keys(self):\n                                manifestations={}, paths={},\n                                devices={uuid4(): FilePath(b\"/tmp\")})\n         self.assertEqual(node_state, wire_decode(wire_encode(node_state)))\n+\n+\n+class StubMigration(object):\n+    \"\"\"\n+    A simple stub migration class, used to test ``migrate_configuration``.\n+    These upgrade methods are not concerned with manipulating the input\n+    configurations; they are used simply to ensure ``migrate_configuration``\n+    follows the correct sequence of method calls to upgrade from version X\n+    to version Y.\n+    \"\"\"\n+    @classmethod\n+    def upgrade_from_v1(cls, config):\n+        config = json.loads(config)\n+        if config['version'] != 1:\n+            raise ConfigurationMigrationError(\n+                \"Supplied configuration was not a valid v1 config.\"\n+            )\n+        return json.dumps({\"version\": 2, \"configuration\": \"fake\"})\n+\n+    @classmethod\n+    def upgrade_from_v2(cls, config):\n+        config = json.loads(config)\n+        if config['version'] != 2:\n+            raise ConfigurationMigrationError(\n+                \"Supplied configuration was not a valid v2 config.\"\n+            )\n+        return json.dumps({\"version\": 3, \"configuration\": \"fake\"})\n+\n+\n+class MigrateConfigurationTests(SynchronousTestCase):\n+    \"\"\"\n+    Tests for ``migrate_configuration``.\n+    \"\"\"\n+    v1_config = json.dumps({\"version\": 1})\n+\n+    def test_error_on_undefined_migration_path(self):\n+        \"\"\"\n+        A ``MissingMigrationError`` is raised if a migration path\n+        from one version to another cannot be found in the supplied\n+        migration class.\n+        \"\"\"\n+        e = self.assertRaises(\n+            MissingMigrationError,\n+            migrate_configuration, 1, 4, self.v1_config, StubMigration\n+        )\n+        expected_error = (\n+            u'Unable to find a migration path for a version 3 to '\n+            u'version 4 configuration. No migration method '\n+            u'upgrade_from_v3 could be found.'\n+        )\n+        self.assertEqual(e.message, expected_error)\n+\n+    def test_sequential_migrations(self):\n+        \"\"\"\n+        A migration from one configuration version to another will\n+        sequentially perform all necessary upgrades, e.g. v1 to v2 followed\n+        by v2 to v3.\n+        \"\"\"\n+        # Get a valid v2 config.\n+        v2_config = migrate_configuration(1, 2, self.v1_config, StubMigration)\n+        # Perform two sequential migrations to get from v1 to v3, starting\n+        # with a v1 config.\n+        result = migrate_configuration(1, 3, self.v1_config, StubMigration)\n+        # Compare the v1 --> v3 upgrade to the direct result of the\n+        # v2 --> v3 upgrade on the v2 config, Both should be identical\n+        # and valid v3 configs.\n+        self.assertEqual(result, StubMigration.upgrade_from_v2(v2_config))\n+\n+\n+UUIDS = st.basic(generate=lambda r, _: UUID(int=r.getrandbits(128)))\n+\n+DATASETS = st.builds(Dataset, dataset_id=UUIDS, maximum_size=st.integers())\n+\n+# Constrain primary to be True so that we don't get invariant errors from Node\n+# due to having two differing manifestations of the same dataset id.\n+MANIFESTATIONS = st.builds(\n+    Manifestation, primary=st.just(True), dataset=DATASETS)\n+SIMPLE_TEXT = st.text(\n+    alphabet=string.letters, min_size=4, max_size=20, average_size=12\n+)\n+IMAGES = st.builds(DockerImage, tag=SIMPLE_TEXT, repository=SIMPLE_TEXT)\n+NONE_OR_INT = st.one_of(\n+    st.just(None),\n+    st.integers()\n+)\n+ST_PORTS = st.integers(min_value=1, max_value=65535)\n+PORTS = st.builds(\n+    Port,\n+    internal_port=ST_PORTS,\n+    external_port=ST_PORTS\n+)\n+LINKS = st.builds(\n+    Link,\n+    local_port=ST_PORTS,\n+    remote_port=ST_PORTS,\n+    alias=SIMPLE_TEXT\n+)\n+APPLICATIONS = st.builds(\n+    Application, name=SIMPLE_TEXT, image=IMAGES,\n+    ports=st.sets(PORTS, max_size=10),\n+    links=st.sets(LINKS, max_size=10),\n+    environment=st.dictionaries(keys=SIMPLE_TEXT, values=SIMPLE_TEXT),\n+    memory_limit=NONE_OR_INT,\n+    cpu_shares=NONE_OR_INT,\n+    running=st.booleans()\n+)\n+FILEPATHS = st.text(alphabet=string.printable).map(FilePath)\n+VOLUMES = st.builds(\n+    AttachedVolume, manifestation=MANIFESTATIONS, mountpoint=FILEPATHS)\n+APPLICATIONS_WITH_VOLUMES = st.tuples(\n+    APPLICATIONS, VOLUMES).map(lambda (a, v): a.set(volume=v))\n+\n+\n+def _build_node(applications):\n+    # All the manifestations in `applications`.\n+    app_manifestations = set(app.volume.manifestation for app in applications)\n+    # A set that contains all of those, plus an arbitrary set of\n+    # manifestations.\n+    dataset_ids = frozenset(\n+        app.volume.manifestation.dataset_id\n+        for app in applications if app.volume\n+    )\n+    manifestations = (\n+        st.sets(MANIFESTATIONS.filter(\n+            lambda m: m.dataset_id not in dataset_ids))\n+        .map(pset)\n+        .map(lambda ms: ms.union(app_manifestations))\n+        .map(lambda ms: dict((m.dataset.dataset_id, m) for m in ms)))\n+    return st.builds(\n+        Node, uuid=UUIDS,\n+        applications=st.just(applications),\n+        manifestations=manifestations)\n+\n+\n+NODES = st.sets(APPLICATIONS_WITH_VOLUMES).map(\n+    lambda apps: pset(dict((\n+        app.volume.manifestation.dataset_id, app) for app in apps).values())\n+).flatmap(_build_node)\n+\n+\n+DEPLOYMENTS = st.builds(\n+    Deployment, nodes=st.sets(NODES, min_size=0, max_size=3)\n+)\n+\n+\n+class ConfigurationMigrationTests(SynchronousTestCase):\n+    \"\"\"\n+    Tests for ``ConfigurationMigration`` class that performs individual\n+    configuration upgrades.\n+\n+    There should be\n+    \"\"\"\n+    @given(DEPLOYMENTS)\n+    def test_upgrade_configuration_v1_latest(self, deployment):\n+        \"\"\"\n+        Test a range of generated configurations (deployments) can be\n+        upgraded from v1 to the latest configuration version.\n+\n+        This test will need updating for each new configuration version\n+        introduced to reflect the expected configuration format\n+        (``expected_configuration``) after a successful upgrade.\n+        \"\"\"\n+        source_json = wire_encode(deployment)\n+        upgraded_json = migrate_configuration(\n+            1, _CONFIG_VERSION, source_json, ConfigurationMigration)\n+        upgraded_config = wire_decode(upgraded_json)\n+        expected_configuration = Configuration(\n+            version=_CONFIG_VERSION, deployment=deployment\n+        )\n+        self.assertEqual(upgraded_config, expected_configuration)\n+\n+    @given(st.tuples(\n+        st.integers(min_value=1, max_value=2),\n+        st.integers(min_value=2, max_value=2)\n+    ))\n+    def test_upgrade_configuration_versions(self, versions):\n+        \"\"\"\n+        Test a range of version upgrades by ensuring the configuration\n+        blob after upgrade matches that which is expected for the\n+        particular version.\n+\n+        See flocker/control/test/configurations for individual\n+        version JSON files and generation code.\n+\n+        Increment the given integer values in the decorator above to define\n+        the test range when new configuration upgraders are added. The first\n+        range is the \"upgrade from\" versions range, where min_value should\n+        always be 1. The second range is the \"upgrade to\" versions range,\n+        where min_value should be 2 and max_value should be the latest\n+        supported configuration version number.","path":"flocker/control/test/test_persistence.py","position":331,"original_position":331,"commit_id":"d6ff38035f3ddc2fcb6685e4dd3419a894a5c82a","original_commit_id":"d6ff38035f3ddc2fcb6685e4dd3419a894a5c82a","user":{"login":"jml","id":125674,"avatar_url":"https://avatars.githubusercontent.com/u/125674?v=3","gravatar_id":"","url":"https://api.github.com/users/jml","html_url":"https://github.com/jml","followers_url":"https://api.github.com/users/jml/followers","following_url":"https://api.github.com/users/jml/following{/other_user}","gists_url":"https://api.github.com/users/jml/gists{/gist_id}","starred_url":"https://api.github.com/users/jml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jml/subscriptions","organizations_url":"https://api.github.com/users/jml/orgs","repos_url":"https://api.github.com/users/jml/repos","events_url":"https://api.github.com/users/jml/events{/privacy}","received_events_url":"https://api.github.com/users/jml/received_events","type":"User","site_admin":false},"body":"Also, it's not clear why we are testing migration from 2 to 2 but not 1 to 1. Please update the docstring to explain why.","created_at":"2015-08-20T14:40:36Z","updated_at":"2015-08-20T14:40:36Z","html_url":"https://github.com/ClusterHQ/flocker/pull/1845#discussion_r37536648","pull_request_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845","_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments/37536648"},"html":{"href":"https://github.com/ClusterHQ/flocker/pull/1845#discussion_r37536648"},"pull_request":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845"}}},"pull_request":{"url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845","id":42437750,"html_url":"https://github.com/ClusterHQ/flocker/pull/1845","diff_url":"https://github.com/ClusterHQ/flocker/pull/1845.diff","patch_url":"https://github.com/ClusterHQ/flocker/pull/1845.patch","issue_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/1845","number":1845,"state":"open","locked":false,"title":"[FLOC-2733] Versioned configuration","user":{"login":"dwgebler","id":646585,"avatar_url":"https://avatars.githubusercontent.com/u/646585?v=3","gravatar_id":"","url":"https://api.github.com/users/dwgebler","html_url":"https://github.com/dwgebler","followers_url":"https://api.github.com/users/dwgebler/followers","following_url":"https://api.github.com/users/dwgebler/following{/other_user}","gists_url":"https://api.github.com/users/dwgebler/gists{/gist_id}","starred_url":"https://api.github.com/users/dwgebler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dwgebler/subscriptions","organizations_url":"https://api.github.com/users/dwgebler/orgs","repos_url":"https://api.github.com/users/dwgebler/repos","events_url":"https://api.github.com/users/dwgebler/events{/privacy}","received_events_url":"https://api.github.com/users/dwgebler/received_events","type":"User","site_admin":false},"body":"Fixes FLOC-2733.\n\n<!-- Reviewable:start -->\n[<img src=\"https://reviewable.io/review_button.png\" height=40 alt=\"Review on Reviewable\"/>](https://reviewable.io/reviews/clusterhq/flocker/1845)\n<!-- Reviewable:end -->\n","created_at":"2015-08-14T09:23:18Z","updated_at":"2015-08-20T14:40:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"dd22e93858b5ed5b52be8c645f760a3eefd446f4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845/commits","review_comments_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845/comments","review_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/1845/comments","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/d6ff38035f3ddc2fcb6685e4dd3419a894a5c82a","head":{"label":"ClusterHQ:versioned-configuration-FLOC-2733","ref":"versioned-configuration-FLOC-2733","sha":"d6ff38035f3ddc2fcb6685e4dd3419a894a5c82a","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":19248542,"name":"flocker","full_name":"ClusterHQ/flocker","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/flocker","description":"Easily manage Docker containers & their data","fork":false,"url":"https://api.github.com/repos/ClusterHQ/flocker","forks_url":"https://api.github.com/repos/ClusterHQ/flocker/forks","keys_url":"https://api.github.com/repos/ClusterHQ/flocker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/flocker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/flocker/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/flocker/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/flocker/events","assignees_url":"https://api.github.com/repos/ClusterHQ/flocker/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/flocker/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/flocker/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/flocker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/flocker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/flocker/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/flocker/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/flocker/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/flocker/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/flocker/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/flocker/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/flocker/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/flocker/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/flocker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/flocker/merges","archive_url":"https://api.github.com/repos/ClusterHQ/flocker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/flocker/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/flocker/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/flocker/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/flocker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/flocker/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/flocker/releases{/id}","created_at":"2014-04-28T18:02:14Z","updated_at":"2015-08-20T07:41:50Z","pushed_at":"2015-08-20T13:43:42Z","git_url":"git://github.com/ClusterHQ/flocker.git","ssh_url":"git@github.com:ClusterHQ/flocker.git","clone_url":"https://github.com/ClusterHQ/flocker.git","svn_url":"https://github.com/ClusterHQ/flocker","homepage":"https://clusterhq.com","size":63435,"stargazers_count":1778,"watchers_count":1778,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":120,"mirror_url":null,"open_issues_count":25,"forks":120,"open_issues":25,"watchers":1778,"default_branch":"master"}},"base":{"label":"ClusterHQ:master","ref":"master","sha":"0134b3ecc25a39b6c103690762a28a796ee1d26a","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":19248542,"name":"flocker","full_name":"ClusterHQ/flocker","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/flocker","description":"Easily manage Docker containers & their data","fork":false,"url":"https://api.github.com/repos/ClusterHQ/flocker","forks_url":"https://api.github.com/repos/ClusterHQ/flocker/forks","keys_url":"https://api.github.com/repos/ClusterHQ/flocker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/flocker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/flocker/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/flocker/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/flocker/events","assignees_url":"https://api.github.com/repos/ClusterHQ/flocker/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/flocker/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/flocker/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/flocker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/flocker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/flocker/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/flocker/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/flocker/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/flocker/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/flocker/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/flocker/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/flocker/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/flocker/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/flocker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/flocker/merges","archive_url":"https://api.github.com/repos/ClusterHQ/flocker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/flocker/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/flocker/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/flocker/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/flocker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/flocker/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/flocker/releases{/id}","created_at":"2014-04-28T18:02:14Z","updated_at":"2015-08-20T07:41:50Z","pushed_at":"2015-08-20T13:43:42Z","git_url":"git://github.com/ClusterHQ/flocker.git","ssh_url":"git@github.com:ClusterHQ/flocker.git","clone_url":"https://github.com/ClusterHQ/flocker.git","svn_url":"https://github.com/ClusterHQ/flocker","homepage":"https://clusterhq.com","size":63435,"stargazers_count":1778,"watchers_count":1778,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":120,"mirror_url":null,"open_issues_count":25,"forks":120,"open_issues":25,"watchers":1778,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845"},"html":{"href":"https://github.com/ClusterHQ/flocker/pull/1845"},"issue":{"href":"https://api.github.com/repos/ClusterHQ/flocker/issues/1845"},"comments":{"href":"https://api.github.com/repos/ClusterHQ/flocker/issues/1845/comments"},"review_comments":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845/comments"},"review_comment":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/1845/commits"},"statuses":{"href":"https://api.github.com/repos/ClusterHQ/flocker/statuses/d6ff38035f3ddc2fcb6685e4dd3419a894a5c82a"}}}}