{"action":"created","comment":{"url":"https://api.github.com/repos/ceph/teuthology/pulls/comments/28094681","id":28094681,"diff_hunk":"@@ -29,105 +30,200 @@ def _get_downburst_exec():\n     import pwd\n     little_old_me = pwd.getpwuid(os.getuid()).pw_name\n     for user in [little_old_me, 'ubuntu', 'teuthology']:\n-        pth = os.path.expanduser(\"~%s/src/downburst/virtualenv/bin/downburst\"\n-                                 % user)\n+        pth = os.path.expanduser(\n+            \"~%s/src/downburst/virtualenv/bin/downburst\" % user)\n         if os.access(pth, os.X_OK):\n             return pth\n     return ''\n \n \n-def create_if_vm(ctx, machine_name):\n+class Downburst(object):\n     \"\"\"\n-    Use downburst to create a virtual machine\n+    A class that provides methods for creating and destroying virtual machine\n+    instances using downburst: https://github.com/ceph/downburst\n     \"\"\"\n-    status_info = get_status(machine_name)\n-    if not status_info.get('is_vm', False):\n-        return False\n-    phys_host = decanonicalize_hostname(status_info['vm_host']['name'])\n-    os_type = get_distro(ctx)\n-    os_version = get_distro_version(ctx)\n+    def __init__(self, name, os_type, os_version, status=None):\n+        self.name = name\n+        self.os_type = os_type\n+        self.os_version = os_version\n+        self.status = status or get_status(self.name)\n+        self.config_path = None\n+        self.host = decanonicalize_hostname(self.status['vm_host']['name'])\n+        self.executable = downburst_executable()\n \n-    createMe = decanonicalize_hostname(machine_name)\n-    with tempfile.NamedTemporaryFile() as tmp:\n-        has_config = hasattr(ctx, 'config') and ctx.config is not None\n-        if has_config and 'downburst' in ctx.config:\n-            log.warning(\n-                'Usage of a custom downburst config has been deprecated.'\n-            )\n+    def create(self):\n+        \"\"\"\n+        Launch a virtual machine instance.\n \n+        If creation fails because an instance with the specified name is\n+        already running, first destroy it, then try again. This process will\n+        repeat two more times, waiting 60s between tries, before giving up.\n+        \"\"\"\n+        if not self.executable:\n+            log.error(\"No downburst executable found.\")\n+            return False\n+        self.build_config()\n+        success = None\n+        with safe_while(sleep=60, tries=3,\n+                        action=\"downburst create\") as proceed:\n+            while proceed():\n+                (returncode, stdout, stderr) = self._run_create()\n+                if returncode == 0:\n+                    log.info(\"Downburst created %s: %s\" % (self.name,\n+                                                           stdout.strip()))\n+                    success = True\n+                    break\n+                elif stderr:\n+                    # If the guest already exists first destroy then re-create:\n+                    if 'exists' in stderr:\n+                        success = False\n+                        log.info(\"Guest files exist. Re-creating guest: %s\" %\n+                                 (self.name))\n+                        self.destroy()\n+                    else:\n+                        success = False\n+                        log.info(\"Downburst failed on %s: %s\" % (\n+                            self.name, stderr.strip()))\n+                        break\n+            return success\n+\n+    def _run_create(self):\n+        \"\"\"\n+        Used by create(), this method is what actually calls downburst when\n+        creating a virtual machine instance.\n+        \"\"\"\n+        if not self.config_path:\n+            raise ValueError(\"I need a config_path!\")\n+        shortname = decanonicalize_hostname(self.name)\n+\n+        args = [self.executable, '-c', self.host, 'create',\n+                '--meta-data=%s' % self.config_path, shortname,\n+                ]\n         log.info(\"Provisioning a {distro} {distroversion} vps\".format(\n-            distro=os_type,\n-            distroversion=os_version\n+            distro=self.os_type,\n+            distroversion=self.os_version\n         ))\n+        proc = subprocess.Popen(args, stdout=subprocess.PIPE,\n+                                stderr=subprocess.PIPE)\n+        out, err = proc.communicate()\n+        return (proc.returncode, out, err)\n+\n+    def destroy(self):\n+        \"\"\"\n+        Destroy (shutdown and delete) a virtual machine instance.\n+        \"\"\"\n+        executable = self.executable\n+        if not executable:\n+            log.error(\"No downburst executable found.\")\n+            return False\n+        shortname = decanonicalize_hostname(self.name)\n+        args = [executable, '-c', self.host, 'destroy', shortname]\n+        proc = subprocess.Popen(args, stdout=subprocess.PIPE,\n+                                stderr=subprocess.PIPE,)\n+        out, err = proc.communicate()\n+        if err:\n+            log.error(\"Error destroying {machine}: {msg}\".format(\n+                machine=self.name, msg=err))\n+            return False\n+        elif proc.returncode == 0:\n+            out_str = ': %s' % out if out else ''\n+            log.info(\"Destroyed %s%s\" % (self.name, out_str))\n+            return True\n+        else:\n+            log.error(\"I don't know if the destroy of {node} succeded!\".format(\n+                node=self.name))\n+            return False\n+\n+    def build_config(self):\n+        \"\"\"\n+        Assemble a configuration to pass to downburst, and write it to a file.\n+        \"\"\"\n+        config_fd = tempfile.NamedTemporaryFile(delete=False)\n \n         file_info = {\n             'disk-size': '100G',\n             'ram': '1.9G',\n             'cpus': 1,\n             'networks': [\n-                {'source': 'front', 'mac': status_info['mac_address']}],\n-            'distro': os_type.lower(),\n-            'distroversion': os_version,\n+                {'source': 'front', 'mac': self.status['mac_address']}],\n+            'distro': self.os_type.lower(),\n+            'distroversion': self.os_version,\n             'additional-disks': 3,\n             'additional-disks-size': '200G',\n             'arch': 'x86_64',\n         }\n-        fqdn = machine_name.split('@')[1]\n+        fqdn = self.name.split('@')[1]\n         file_out = {'downburst': file_info, 'local-hostname': fqdn}\n-        yaml.safe_dump(file_out, tmp)\n-        metadata = \"--meta-data=%s\" % tmp.name\n-        dbrst = _get_downburst_exec()\n-        if not dbrst:\n-            log.error(\"No downburst executable found.\")\n-            return False\n-        p = subprocess.Popen([dbrst, '-c', phys_host,\n-                              'create', metadata, createMe],\n-                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,)\n-        owt, err = p.communicate()\n-        if err:\n-            log.info(\"Downburst completed on %s: %s\" %\n-                     (machine_name, err))\n-        else:\n-            log.info(\"%s created: %s\" % (machine_name, owt))\n-        # If the guest already exists first destroy then re-create:\n-        if 'exists' in err:\n-            log.info(\"Guest files exist. Re-creating guest: %s\" %\n-                     (machine_name))\n-            destroy_if_vm(ctx, machine_name)\n-            create_if_vm(ctx, machine_name)\n-    return True\n+        yaml.safe_dump(file_out, config_fd)\n+        self.config_path = config_fd.name\n+        return True\n+\n+    def remove_config(self):\n+        \"\"\"\n+        Remove the downburst configuration file created by build_config()\n+        \"\"\"\n+        if self.config_path and os.path.exists(self.config_path):\n+            os.remove(self.config_path)\n+            self.config_path = None\n+            return True\n+        return False\n+\n+    def __del__(self):\n+        self.remove_config()\n \n \n-def destroy_if_vm(ctx, machine_name, user=None, description=None):\n+def create_if_vm(ctx, machine_name, _downburst=None):","path":"teuthology/provision.py","position":209,"original_position":209,"commit_id":"df618e62c6ce46668ab58b7baa8b10f67d055999","original_commit_id":"df618e62c6ce46668ab58b7baa8b10f67d055999","user":{"login":"andrewschoen","id":20618,"avatar_url":"https://avatars.githubusercontent.com/u/20618?v=3","gravatar_id":"","url":"https://api.github.com/users/andrewschoen","html_url":"https://github.com/andrewschoen","followers_url":"https://api.github.com/users/andrewschoen/followers","following_url":"https://api.github.com/users/andrewschoen/following{/other_user}","gists_url":"https://api.github.com/users/andrewschoen/gists{/gist_id}","starred_url":"https://api.github.com/users/andrewschoen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrewschoen/subscriptions","organizations_url":"https://api.github.com/users/andrewschoen/orgs","repos_url":"https://api.github.com/users/andrewschoen/repos","events_url":"https://api.github.com/users/andrewschoen/events{/privacy}","received_events_url":"https://api.github.com/users/andrewschoen/received_events","type":"User","site_admin":false},"body":"Would you please mention in the comments that the _downburst kwarg is used only for testing? The destroy method needs this as well.","created_at":"2015-04-09T19:32:41Z","updated_at":"2015-04-09T19:32:41Z","html_url":"https://github.com/ceph/teuthology/pull/459#discussion_r28094681","pull_request_url":"https://api.github.com/repos/ceph/teuthology/pulls/459","_links":{"self":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/comments/28094681"},"html":{"href":"https://github.com/ceph/teuthology/pull/459#discussion_r28094681"},"pull_request":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/459"}}},"pull_request":{"url":"https://api.github.com/repos/ceph/teuthology/pulls/459","id":32704426,"html_url":"https://github.com/ceph/teuthology/pull/459","diff_url":"https://github.com/ceph/teuthology/pull/459.diff","patch_url":"https://github.com/ceph/teuthology/pull/459.patch","issue_url":"https://api.github.com/repos/ceph/teuthology/issues/459","number":459,"state":"open","locked":false,"title":"Refactor downburst provisioning","user":{"login":"zmc","id":58307,"avatar_url":"https://avatars.githubusercontent.com/u/58307?v=3","gravatar_id":"","url":"https://api.github.com/users/zmc","html_url":"https://github.com/zmc","followers_url":"https://api.github.com/users/zmc/followers","following_url":"https://api.github.com/users/zmc/following{/other_user}","gists_url":"https://api.github.com/users/zmc/gists{/gist_id}","starred_url":"https://api.github.com/users/zmc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zmc/subscriptions","organizations_url":"https://api.github.com/users/zmc/orgs","repos_url":"https://api.github.com/users/zmc/repos","events_url":"https://api.github.com/users/zmc/events{/privacy}","received_events_url":"https://api.github.com/users/zmc/received_events","type":"User","site_admin":false},"body":"This allows for better logging, easier debugging, and a proper way to give up attempting to start up instances that are somehow broken. I also intend for it to serve as a model to follow when adding support for e.g. Cobbler reimaging and OpenStack provisioning.","created_at":"2015-04-06T19:42:21Z","updated_at":"2015-04-09T19:32:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"39bbe07a30f4c581579683cf0c6454a7e5bfbc72","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ceph/teuthology/pulls/459/commits","review_comments_url":"https://api.github.com/repos/ceph/teuthology/pulls/459/comments","review_comment_url":"https://api.github.com/repos/ceph/teuthology/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ceph/teuthology/issues/459/comments","statuses_url":"https://api.github.com/repos/ceph/teuthology/statuses/df618e62c6ce46668ab58b7baa8b10f67d055999","head":{"label":"ceph:wip-downburst","ref":"wip-downburst","sha":"df618e62c6ce46668ab58b7baa8b10f67d055999","user":{"login":"ceph","id":1015767,"avatar_url":"https://avatars.githubusercontent.com/u/1015767?v=3","gravatar_id":"","url":"https://api.github.com/users/ceph","html_url":"https://github.com/ceph","followers_url":"https://api.github.com/users/ceph/followers","following_url":"https://api.github.com/users/ceph/following{/other_user}","gists_url":"https://api.github.com/users/ceph/gists{/gist_id}","starred_url":"https://api.github.com/users/ceph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceph/subscriptions","organizations_url":"https://api.github.com/users/ceph/orgs","repos_url":"https://api.github.com/users/ceph/repos","events_url":"https://api.github.com/users/ceph/events{/privacy}","received_events_url":"https://api.github.com/users/ceph/received_events","type":"Organization","site_admin":false},"repo":{"id":2357747,"name":"teuthology","full_name":"ceph/teuthology","owner":{"login":"ceph","id":1015767,"avatar_url":"https://avatars.githubusercontent.com/u/1015767?v=3","gravatar_id":"","url":"https://api.github.com/users/ceph","html_url":"https://github.com/ceph","followers_url":"https://api.github.com/users/ceph/followers","following_url":"https://api.github.com/users/ceph/following{/other_user}","gists_url":"https://api.github.com/users/ceph/gists{/gist_id}","starred_url":"https://api.github.com/users/ceph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceph/subscriptions","organizations_url":"https://api.github.com/users/ceph/orgs","repos_url":"https://api.github.com/users/ceph/repos","events_url":"https://api.github.com/users/ceph/events{/privacy}","received_events_url":"https://api.github.com/users/ceph/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ceph/teuthology","description":"Ceph test suite","fork":false,"url":"https://api.github.com/repos/ceph/teuthology","forks_url":"https://api.github.com/repos/ceph/teuthology/forks","keys_url":"https://api.github.com/repos/ceph/teuthology/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ceph/teuthology/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ceph/teuthology/teams","hooks_url":"https://api.github.com/repos/ceph/teuthology/hooks","issue_events_url":"https://api.github.com/repos/ceph/teuthology/issues/events{/number}","events_url":"https://api.github.com/repos/ceph/teuthology/events","assignees_url":"https://api.github.com/repos/ceph/teuthology/assignees{/user}","branches_url":"https://api.github.com/repos/ceph/teuthology/branches{/branch}","tags_url":"https://api.github.com/repos/ceph/teuthology/tags","blobs_url":"https://api.github.com/repos/ceph/teuthology/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ceph/teuthology/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ceph/teuthology/git/refs{/sha}","trees_url":"https://api.github.com/repos/ceph/teuthology/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ceph/teuthology/statuses/{sha}","languages_url":"https://api.github.com/repos/ceph/teuthology/languages","stargazers_url":"https://api.github.com/repos/ceph/teuthology/stargazers","contributors_url":"https://api.github.com/repos/ceph/teuthology/contributors","subscribers_url":"https://api.github.com/repos/ceph/teuthology/subscribers","subscription_url":"https://api.github.com/repos/ceph/teuthology/subscription","commits_url":"https://api.github.com/repos/ceph/teuthology/commits{/sha}","git_commits_url":"https://api.github.com/repos/ceph/teuthology/git/commits{/sha}","comments_url":"https://api.github.com/repos/ceph/teuthology/comments{/number}","issue_comment_url":"https://api.github.com/repos/ceph/teuthology/issues/comments{/number}","contents_url":"https://api.github.com/repos/ceph/teuthology/contents/{+path}","compare_url":"https://api.github.com/repos/ceph/teuthology/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ceph/teuthology/merges","archive_url":"https://api.github.com/repos/ceph/teuthology/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ceph/teuthology/downloads","issues_url":"https://api.github.com/repos/ceph/teuthology/issues{/number}","pulls_url":"https://api.github.com/repos/ceph/teuthology/pulls{/number}","milestones_url":"https://api.github.com/repos/ceph/teuthology/milestones{/number}","notifications_url":"https://api.github.com/repos/ceph/teuthology/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ceph/teuthology/labels{/name}","releases_url":"https://api.github.com/repos/ceph/teuthology/releases{/id}","created_at":"2011-09-09T20:00:28Z","updated_at":"2015-04-07T22:54:07Z","pushed_at":"2015-04-09T18:54:15Z","git_url":"git://github.com/ceph/teuthology.git","ssh_url":"git@github.com:ceph/teuthology.git","clone_url":"https://github.com/ceph/teuthology.git","svn_url":"https://github.com/ceph/teuthology","homepage":"","size":16982,"stargazers_count":38,"watchers_count":38,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":37,"mirror_url":null,"open_issues_count":10,"forks":37,"open_issues":10,"watchers":38,"default_branch":"master"}},"base":{"label":"ceph:master","ref":"master","sha":"91668c831c3cad008ee88b736f0db2b50ad1d1c6","user":{"login":"ceph","id":1015767,"avatar_url":"https://avatars.githubusercontent.com/u/1015767?v=3","gravatar_id":"","url":"https://api.github.com/users/ceph","html_url":"https://github.com/ceph","followers_url":"https://api.github.com/users/ceph/followers","following_url":"https://api.github.com/users/ceph/following{/other_user}","gists_url":"https://api.github.com/users/ceph/gists{/gist_id}","starred_url":"https://api.github.com/users/ceph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceph/subscriptions","organizations_url":"https://api.github.com/users/ceph/orgs","repos_url":"https://api.github.com/users/ceph/repos","events_url":"https://api.github.com/users/ceph/events{/privacy}","received_events_url":"https://api.github.com/users/ceph/received_events","type":"Organization","site_admin":false},"repo":{"id":2357747,"name":"teuthology","full_name":"ceph/teuthology","owner":{"login":"ceph","id":1015767,"avatar_url":"https://avatars.githubusercontent.com/u/1015767?v=3","gravatar_id":"","url":"https://api.github.com/users/ceph","html_url":"https://github.com/ceph","followers_url":"https://api.github.com/users/ceph/followers","following_url":"https://api.github.com/users/ceph/following{/other_user}","gists_url":"https://api.github.com/users/ceph/gists{/gist_id}","starred_url":"https://api.github.com/users/ceph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceph/subscriptions","organizations_url":"https://api.github.com/users/ceph/orgs","repos_url":"https://api.github.com/users/ceph/repos","events_url":"https://api.github.com/users/ceph/events{/privacy}","received_events_url":"https://api.github.com/users/ceph/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ceph/teuthology","description":"Ceph test suite","fork":false,"url":"https://api.github.com/repos/ceph/teuthology","forks_url":"https://api.github.com/repos/ceph/teuthology/forks","keys_url":"https://api.github.com/repos/ceph/teuthology/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ceph/teuthology/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ceph/teuthology/teams","hooks_url":"https://api.github.com/repos/ceph/teuthology/hooks","issue_events_url":"https://api.github.com/repos/ceph/teuthology/issues/events{/number}","events_url":"https://api.github.com/repos/ceph/teuthology/events","assignees_url":"https://api.github.com/repos/ceph/teuthology/assignees{/user}","branches_url":"https://api.github.com/repos/ceph/teuthology/branches{/branch}","tags_url":"https://api.github.com/repos/ceph/teuthology/tags","blobs_url":"https://api.github.com/repos/ceph/teuthology/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ceph/teuthology/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ceph/teuthology/git/refs{/sha}","trees_url":"https://api.github.com/repos/ceph/teuthology/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ceph/teuthology/statuses/{sha}","languages_url":"https://api.github.com/repos/ceph/teuthology/languages","stargazers_url":"https://api.github.com/repos/ceph/teuthology/stargazers","contributors_url":"https://api.github.com/repos/ceph/teuthology/contributors","subscribers_url":"https://api.github.com/repos/ceph/teuthology/subscribers","subscription_url":"https://api.github.com/repos/ceph/teuthology/subscription","commits_url":"https://api.github.com/repos/ceph/teuthology/commits{/sha}","git_commits_url":"https://api.github.com/repos/ceph/teuthology/git/commits{/sha}","comments_url":"https://api.github.com/repos/ceph/teuthology/comments{/number}","issue_comment_url":"https://api.github.com/repos/ceph/teuthology/issues/comments{/number}","contents_url":"https://api.github.com/repos/ceph/teuthology/contents/{+path}","compare_url":"https://api.github.com/repos/ceph/teuthology/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ceph/teuthology/merges","archive_url":"https://api.github.com/repos/ceph/teuthology/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ceph/teuthology/downloads","issues_url":"https://api.github.com/repos/ceph/teuthology/issues{/number}","pulls_url":"https://api.github.com/repos/ceph/teuthology/pulls{/number}","milestones_url":"https://api.github.com/repos/ceph/teuthology/milestones{/number}","notifications_url":"https://api.github.com/repos/ceph/teuthology/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ceph/teuthology/labels{/name}","releases_url":"https://api.github.com/repos/ceph/teuthology/releases{/id}","created_at":"2011-09-09T20:00:28Z","updated_at":"2015-04-07T22:54:07Z","pushed_at":"2015-04-09T18:54:15Z","git_url":"git://github.com/ceph/teuthology.git","ssh_url":"git@github.com:ceph/teuthology.git","clone_url":"https://github.com/ceph/teuthology.git","svn_url":"https://github.com/ceph/teuthology","homepage":"","size":16982,"stargazers_count":38,"watchers_count":38,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":37,"mirror_url":null,"open_issues_count":10,"forks":37,"open_issues":10,"watchers":38,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/459"},"html":{"href":"https://github.com/ceph/teuthology/pull/459"},"issue":{"href":"https://api.github.com/repos/ceph/teuthology/issues/459"},"comments":{"href":"https://api.github.com/repos/ceph/teuthology/issues/459/comments"},"review_comments":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/459/comments"},"review_comment":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ceph/teuthology/pulls/459/commits"},"statuses":{"href":"https://api.github.com/repos/ceph/teuthology/statuses/df618e62c6ce46668ab58b7baa8b10f67d055999"}}}}