예제 #1
0
    def setUp(self):
        super(UnitMachineDeploymentTest, self).setUp()
        self.charm = get_charm_from_path(self.sample_dir1)
        self.bundle = self.charm.as_bundle()
        self.juju_directory = self.makeDir()
        self.units_directory = os.path.join(self.juju_directory, "units")
        os.mkdir(self.units_directory)
        self.unit_name = "wordpress/0"

        self.deployment = UnitMachineDeployment(self.unit_name,
                                                self.juju_directory)

        self.assertEqual(self.deployment.unit_agent_module, "juju.agents.unit")
        self.deployment.unit_agent_module = "juju.agents.dummy"
예제 #2
0
    def setUp(self):
        super(UnitMachineDeploymentTest, self).setUp()
        self.charm = get_charm_from_path(self.sample_dir1)
        self.bundle = self.charm.as_bundle()
        self.juju_directory = self.makeDir()
        self.units_directory = os.path.join(self.juju_directory, "units")
        os.mkdir(self.units_directory)
        self.unit_name = "wordpress/0"

        self.deployment = UnitMachineDeployment(
            self.unit_name,
            self.juju_directory)

        self.assertEqual(
            self.deployment.unit_agent_module, "juju.agents.unit")
        self.deployment.unit_agent_module = "juju.agents.dummy"
예제 #3
0
class UnitMachineDeploymentTest(RepositoryTestBase):
    def setUp(self):
        super(UnitMachineDeploymentTest, self).setUp()
        self.charm = get_charm_from_path(self.sample_dir1)
        self.bundle = self.charm.as_bundle()
        self.juju_directory = self.makeDir()
        self.units_directory = os.path.join(self.juju_directory, "units")
        os.mkdir(self.units_directory)
        self.unit_name = "wordpress/0"

        self.deployment = UnitMachineDeployment(self.unit_name,
                                                self.juju_directory)

        self.assertEqual(self.deployment.unit_agent_module, "juju.agents.unit")
        self.deployment.unit_agent_module = "juju.agents.dummy"

    def process_kill(self, pid):
        try:
            os.kill(pid, 9)
        except OSError:
            pass

    def test_unit_name_with_path_manipulation_raises_assertion(self):
        self.assertRaises(AssertionError, UnitMachineDeployment,
                          "../../etc/password/zebra/0", self.units_directory)

    def test_unit_directory(self):
        self.assertEqual(
            self.deployment.directory,
            os.path.join(self.units_directory,
                         self.unit_name.replace("/", "-")))

    def test_unit_pid_file(self):
        self.assertEqual(
            self.deployment.pid_file,
            os.path.join(self.units_directory,
                         "%s.pid" % (self.unit_name.replace("/", "-"))))

    def test_service_unit_start(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """

        d = self.deployment.start("0", get_test_zookeeper_address(),
                                  self.bundle)

        @inlineCallbacks
        def validate_result(result):
            # give process time to write its pid
            yield self.sleep(0.1)
            self.addCleanup(self.process_kill,
                            int(open(self.deployment.pid_file).read()))
            self.assertEqual(result, True)

        d.addCallback(validate_result)
        return d

    def test_deployment_get_environment(self):
        zk_address = get_test_zookeeper_address()
        environ = self.deployment.get_environment(21, zk_address)
        environ.pop("PYTHONPATH")
        self.assertEqual(environ["JUJU_HOME"], self.juju_directory)
        self.assertEqual(environ["JUJU_UNIT_NAME"], self.unit_name)
        self.assertEqual(environ["JUJU_ZOOKEEPER"], zk_address)
        self.assertEqual(environ["JUJU_MACHINE_ID"], "21")

    def test_service_unit_start_with_integer_machine_id(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """
        d = self.deployment.start(21, get_test_zookeeper_address(),
                                  self.bundle)

        @inlineCallbacks
        def validate_result(result):
            # give process time to write its pid
            yield self.sleep(0.1)
            self.addCleanup(self.process_kill,
                            int(open(self.deployment.pid_file).read()))
            self.assertEqual(result, True)

        d.addCallback(validate_result)
        return d

    def test_service_unit_start_with_agent_startup_error(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """
        self.deployment.unit_agent_module = "magichat.xr1"
        d = self.deployment.start("0", get_test_zookeeper_address(),
                                  self.bundle)

        self.failUnlessFailure(d, UnitDeploymentError)

        def validate_result(error):
            self.assertIn("No module named magichat", str(error))

        d.addCallback(validate_result)
        return d

    def test_service_unit_start_agent_arguments(self):
        """
        Starting a service unit will start a service unit agent with arguments
        denoting the current machine id, zookeeper server location, and the
        unit name. Additionally it will configure the log and pid file
        locations.
        """
        machine_id = "0"
        zookeeper_hosts = "menagerie.example.com:2181"

        from twisted.internet import reactor
        mock_reactor = self.mocker.patch(reactor)

        environ = dict(os.environ)
        environ["JUJU_UNIT_NAME"] = self.unit_name
        environ["JUJU_HOME"] = self.juju_directory
        environ["JUJU_MACHINE_ID"] = machine_id
        environ["JUJU_ZOOKEEPER"] = zookeeper_hosts
        environ["PYTHONPATH"] = ":".join(
            filter(None, [
                os.path.dirname(get_module_directory(juju)),
                environ.get("PYTHONPATH")
            ]))

        pid_file = os.path.join(self.units_directory,
                                "%s.pid" % self.unit_name.replace("/", "-"))

        log_file = os.path.join(self.deployment.directory, "charm.log")

        args = [
            sys.executable, "-m", "juju.agents.dummy", "-n", "--pidfile",
            pid_file, "--logfile", log_file
        ]

        mock_reactor.spawnProcess(MATCH_PROTOCOL, sys.executable, args,
                                  environ)
        self.mocker.replay()
        self.deployment.start(machine_id, zookeeper_hosts, self.bundle)

    def xtest_service_unit_start_pre_unpack(self):
        """
        Attempting to start a charm before the charm is unpacked
        results in an exception.
        """
        error = yield self.assertFailure(
            self.deployment.start("0", get_test_zookeeper_address(),
                                  self.bundle), UnitDeploymentError)
        self.assertEquals(str(error), "Charm must be unpacked first.")

    @inlineCallbacks
    def test_service_unit_destroy(self):
        """
        Forcibly stop a unit, and destroy any directories associated to it
        on the machine, and kills the unit agent process.
        """
        yield self.deployment.start("0", get_test_zookeeper_address(),
                                    self.bundle)
        # give the process time to write its pid file
        yield self.sleep(0.1)
        pid = int(open(self.deployment.pid_file).read())
        yield self.deployment.destroy()
        # give the process time to die.
        yield self.sleep(0.1)
        e = self.assertRaises(OSError, os.kill, pid, 0)
        self.assertEqual(e.errno, 3)
        self.assertFalse(os.path.exists(self.deployment.directory))
        self.assertFalse(os.path.exists(self.deployment.pid_file))

    def test_service_unit_destroy_stale_pid(self):
        """
        A stale pid file does not cause any errors.

        We mock away is_running as otherwise it will check for this, but
        there exists a small window when the result may disagree.
        """
        self.makeFile("8917238", path=self.deployment.pid_file)
        mock_deployment = self.mocker.patch(self.deployment)
        mock_deployment.is_running()
        self.mocker.result(succeed(True))
        self.mocker.replay()
        return self.deployment.destroy()

    def test_service_unit_destroy_perm_error(self):
        """
        A stale pid file does not cause any errors.

        We mock away is_running as otherwise it will check for this, but
        there exists a small window when the result may disagree.
        """
        if os.geteuid() == 0:
            return
        self.makeFile("1", path=self.deployment.pid_file)
        mock_deployment = self.mocker.patch(self.deployment)
        mock_deployment.is_running()
        self.mocker.result(succeed(True))
        self.mocker.replay()
        return self.assertFailure(self.deployment.destroy(), OSError)

    @inlineCallbacks
    def test_service_unit_destroy_undeployed(self):
        """
        If the unit is has not been deployed, nothing happens.
        """
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

    @inlineCallbacks
    def test_service_unit_destroy_not_running(self):
        """
        If the unit is not running, then destroy will just remove
        its directory.
        """
        self.deployment.unpack_charm(self.bundle)
        self.assertTrue(os.path.exists(self.deployment.directory))
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

    def test_unpack_charm(self):
        """
        The deployment unpacks a charm bundle into the unit workspace.
        """
        self.deployment.unpack_charm(self.bundle)
        unit_path = os.path.join(self.units_directory,
                                 self.unit_name.replace("/", "-"))

        self.assertTrue(os.path.exists(unit_path))
        charm_path = os.path.join(unit_path, "charm")
        self.assertTrue(os.path.exists(charm_path))
        charm = get_charm_from_path(charm_path)
        self.assertEqual(charm.get_revision(), self.charm.get_revision())

    def test_unpack_charm_exception_invalid_charm(self):
        """
        If the charm bundle is corrupted or invalid a deployment specific
        error is raised.
        """
        error = self.assertRaises(UnitDeploymentError,
                                  self.deployment.unpack_charm, self.charm)
        self.assertEquals(str(error),
                          "Invalid charm for deployment: %s" % self.charm.path)

    def test_is_running_no_pid_file(self):
        """
        If there is no pid file the service unit is not running.
        """
        self.assertEqual((yield self.deployment.is_running()), False)

    def test_is_running(self):
        """
        The service deployment will check the pid and validate
        that the pid found is a running process.
        """
        self.makeFile(str(os.getpid()), path=self.deployment.pid_file)
        self.assertEqual((yield self.deployment.is_running()), True)

    def test_is_running_against_unknown_error(self):
        """
        If we don't have permission to access the process, the
        original error should get passed along.
        """
        if os.geteuid() == 0:
            return
        self.makeFile("1", path=self.deployment.pid_file)
        self.assertFailure(self.deployment.is_running(), OSError)

    def test_is_running_invalid_pid_file(self):
        """
        If the pid file is corrupted on disk, and does not contain
        a valid integer, then the agent is not running.
        """
        self.makeFile("abcdef", path=self.deployment.pid_file)
        self.assertEqual((yield self.deployment.is_running()), False)

    def test_is_running_invalid_pid(self):
        """
        If the pid file refers to an invalid process then the
        agent is not running.
        """
        self.makeFile("669966", path=self.deployment.pid_file)
        self.assertEqual((yield self.deployment.is_running()), False)
예제 #4
0
class UnitMachineDeploymentTest(RepositoryTestBase):

    def setUp(self):
        super(UnitMachineDeploymentTest, self).setUp()
        self.charm = get_charm_from_path(self.sample_dir1)
        self.bundle = self.charm.as_bundle()
        self.juju_directory = self.makeDir()
        self.units_directory = os.path.join(self.juju_directory, "units")
        os.mkdir(self.units_directory)
        self.unit_name = "wordpress/0"

        self.deployment = UnitMachineDeployment(
            self.unit_name,
            self.juju_directory)

        self.assertEqual(
            self.deployment.unit_agent_module, "juju.agents.unit")
        self.deployment.unit_agent_module = "juju.agents.dummy"

    def process_kill(self, pid):
        try:
            os.kill(pid, 9)
        except OSError:
            pass

    def test_unit_name_with_path_manipulation_raises_assertion(self):
        self.assertRaises(
            AssertionError,
            UnitMachineDeployment,
            "../../etc/password/zebra/0",
            self.units_directory)

    def test_unit_directory(self):
        self.assertEqual(
            self.deployment.directory,
            os.path.join(self.units_directory,
                         self.unit_name.replace("/", "-")))

    def test_unit_pid_file(self):
        self.assertEqual(
            self.deployment.pid_file,
            os.path.join(self.units_directory,
                         "%s.pid" % (self.unit_name.replace("/", "-"))))

    def test_service_unit_start(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """

        d = self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)

        @inlineCallbacks
        def validate_result(result):
            # give process time to write its pid
            yield self.sleep(0.1)
            self.addCleanup(
                self.process_kill,
                int(open(self.deployment.pid_file).read()))
            self.assertEqual(result, True)

        d.addCallback(validate_result)
        return d

    def test_deployment_get_environment(self):
        zk_address = get_test_zookeeper_address()
        environ = self.deployment.get_environment(21, zk_address)
        environ.pop("PYTHONPATH")
        self.assertEqual(environ["JUJU_HOME"], self.juju_directory)
        self.assertEqual(environ["JUJU_UNIT_NAME"], self.unit_name)
        self.assertEqual(environ["JUJU_ZOOKEEPER"], zk_address)
        self.assertEqual(environ["JUJU_MACHINE_ID"], "21")

    def test_service_unit_start_with_integer_machine_id(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """
        d = self.deployment.start(
            21, get_test_zookeeper_address(), self.bundle)

        @inlineCallbacks
        def validate_result(result):
            # give process time to write its pid
            yield self.sleep(0.1)
            self.addCleanup(
                self.process_kill,
                int(open(self.deployment.pid_file).read()))
            self.assertEqual(result, True)

        d.addCallback(validate_result)
        return d

    def test_service_unit_start_with_agent_startup_error(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """
        self.deployment.unit_agent_module = "magichat.xr1"
        d = self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)

        self.failUnlessFailure(d, UnitDeploymentError)

        def validate_result(error):
            self.assertIn("No module named magichat", str(error))

        d.addCallback(validate_result)
        return d

    def test_service_unit_start_agent_arguments(self):
        """
        Starting a service unit will start a service unit agent with arguments
        denoting the current machine id, zookeeper server location, and the
        unit name. Additionally it will configure the log and pid file
        locations.
        """
        machine_id = "0"
        zookeeper_hosts = "menagerie.example.com:2181"

        from twisted.internet import reactor
        mock_reactor = self.mocker.patch(reactor)

        environ = dict(os.environ)
        environ["JUJU_UNIT_NAME"] = self.unit_name
        environ["JUJU_HOME"] = self.juju_directory
        environ["JUJU_MACHINE_ID"] = machine_id
        environ["JUJU_ZOOKEEPER"] = zookeeper_hosts
        environ["PYTHONPATH"] = ":".join(
            filter(None, [
                os.path.dirname(get_module_directory(juju)),
                environ.get("PYTHONPATH")]))

        pid_file = os.path.join(
            self.units_directory,
            "%s.pid" % self.unit_name.replace("/", "-"))

        log_file = os.path.join(
            self.deployment.directory,
            "charm.log")

        args = [sys.executable, "-m", "juju.agents.dummy", "-n",
                "--pidfile", pid_file, "--logfile", log_file]

        mock_reactor.spawnProcess(
            MATCH_PROTOCOL, sys.executable, args, environ)
        self.mocker.replay()
        self.deployment.start(
            machine_id, zookeeper_hosts, self.bundle)

    def xtest_service_unit_start_pre_unpack(self):
        """
        Attempting to start a charm before the charm is unpacked
        results in an exception.
        """
        error = yield self.assertFailure(
            self.deployment.start(
                "0", get_test_zookeeper_address(), self.bundle),
            UnitDeploymentError)
        self.assertEquals(str(error), "Charm must be unpacked first.")

    @inlineCallbacks
    def test_service_unit_destroy(self):
        """
        Forcibly stop a unit, and destroy any directories associated to it
        on the machine, and kills the unit agent process.
        """
        yield self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)
        # give the process time to write its pid file
        yield self.sleep(0.1)
        pid = int(open(self.deployment.pid_file).read())
        yield self.deployment.destroy()
        # give the process time to die.
        yield self.sleep(0.1)
        e = self.assertRaises(OSError, os.kill, pid, 0)
        self.assertEqual(e.errno, 3)
        self.assertFalse(os.path.exists(self.deployment.directory))
        self.assertFalse(os.path.exists(self.deployment.pid_file))

    def test_service_unit_destroy_stale_pid(self):
        """
        A stale pid file does not cause any errors.

        We mock away is_running as otherwise it will check for this, but
        there exists a small window when the result may disagree.
        """
        self.makeFile("8917238", path=self.deployment.pid_file)
        mock_deployment = self.mocker.patch(self.deployment)
        mock_deployment.is_running()
        self.mocker.result(succeed(True))
        self.mocker.replay()
        return self.deployment.destroy()

    def test_service_unit_destroy_perm_error(self):
        """
        A stale pid file does not cause any errors.

        We mock away is_running as otherwise it will check for this, but
        there exists a small window when the result may disagree.
        """
        if os.geteuid() == 0:
            return
        self.makeFile("1", path=self.deployment.pid_file)
        mock_deployment = self.mocker.patch(self.deployment)
        mock_deployment.is_running()
        self.mocker.result(succeed(True))
        self.mocker.replay()
        return self.assertFailure(self.deployment.destroy(), OSError)

    @inlineCallbacks
    def test_service_unit_destroy_undeployed(self):
        """
        If the unit is has not been deployed, nothing happens.
        """
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

    @inlineCallbacks
    def test_service_unit_destroy_not_running(self):
        """
        If the unit is not running, then destroy will just remove
        its directory.
        """
        self.deployment.unpack_charm(self.bundle)
        self.assertTrue(os.path.exists(self.deployment.directory))
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

    def test_unpack_charm(self):
        """
        The deployment unpacks a charm bundle into the unit workspace.
        """
        self.deployment.unpack_charm(self.bundle)
        unit_path = os.path.join(
            self.units_directory, self.unit_name.replace("/", "-"))

        self.assertTrue(os.path.exists(unit_path))
        charm_path = os.path.join(unit_path, "charm")
        self.assertTrue(os.path.exists(charm_path))
        charm = get_charm_from_path(charm_path)
        self.assertEqual(
            charm.get_revision(), self.charm.get_revision())

    def test_unpack_charm_exception_invalid_charm(self):
        """
        If the charm bundle is corrupted or invalid a deployment specific
        error is raised.
        """
        error = self.assertRaises(
            UnitDeploymentError,
            self.deployment.unpack_charm, self.charm)
        self.assertEquals(
            str(error),
            "Invalid charm for deployment: %s" % self.charm.path)

    def test_is_running_no_pid_file(self):
        """
        If there is no pid file the service unit is not running.
        """
        self.assertEqual((yield self.deployment.is_running()), False)

    def test_is_running(self):
        """
        The service deployment will check the pid and validate
        that the pid found is a running process.
        """
        self.makeFile(
            str(os.getpid()), path=self.deployment.pid_file)
        self.assertEqual((yield self.deployment.is_running()), True)

    def test_is_running_against_unknown_error(self):
        """
        If we don't have permission to access the process, the
        original error should get passed along.
        """
        if os.geteuid() == 0:
            return
        self.makeFile("1", path=self.deployment.pid_file)
        self.assertFailure(self.deployment.is_running(), OSError)

    def test_is_running_invalid_pid_file(self):
        """
        If the pid file is corrupted on disk, and does not contain
        a valid integer, then the agent is not running.
        """
        self.makeFile("abcdef", path=self.deployment.pid_file)
        self.assertEqual(
            (yield self.deployment.is_running()), False)

    def test_is_running_invalid_pid(self):
        """
        If the pid file refers to an invalid process then the
        agent is not running.
        """
        self.makeFile("669966", path=self.deployment.pid_file)
        self.assertEqual(
            (yield self.deployment.is_running()), False)
예제 #5
0
class UnitMachineDeploymentTest(RepositoryTestBase):

    def setUp(self):
        super(UnitMachineDeploymentTest, self).setUp()
        self.charm = get_charm_from_path(self.sample_dir1)
        self.bundle = self.charm.as_bundle()
        self.juju_directory = self.makeDir()
        self.units_directory = os.path.join(self.juju_directory, "units")
        os.mkdir(self.units_directory)
        self.unit_name = "wordpress/0"
        self.rootfs = self.makeDir()
        self.init_dir = os.path.join(self.rootfs, "etc", "init")
        os.makedirs(self.init_dir)
        self.real_init_dir = self.patch(
            UpstartService, "init_dir", self.init_dir)

        self.deployment = UnitMachineDeployment(
            self.unit_name,
            self.juju_directory)

        self.assertEqual(
            self.deployment.unit_agent_module, "juju.agents.unit")
        self.deployment.unit_agent_module = "juju.agents.dummy"

    def setup_mock(self):
        self.check_call = self.mocker.replace("subprocess.check_call")
        self.getProcessOutput = self.mocker.replace(
            "twisted.internet.utils.getProcessOutput")

    def mock_is_running(self, running):
        self.getProcessOutput("/sbin/status", ["juju-wordpress-0"])
        if running:
            self.mocker.result(succeed(
                "juju-wordpress-0 start/running, process 12345"))
        else:
            self.mocker.result(succeed("juju-wordpress-0 stop/waiting"))

    def _without_sudo(self, args, **_):
        self.assertEquals(args[0], "sudo")
        return subprocess.call(args[1:])

    def mock_install(self):
        self.check_call(ANY, KWARGS)  # cp to init dir
        self.mocker.call(self._without_sudo)
        self.check_call(ANY, KWARGS)  # chmod 644
        self.mocker.call(self._without_sudo)

    def mock_start(self):
        self.check_call(("sudo", "/sbin/start", "juju-wordpress-0"), KWARGS)
        self.mocker.result(0)
        for _ in range(5):
            self.mock_is_running(True)

    def mock_destroy(self):
        self.check_call(("sudo", "/sbin/stop", "juju-wordpress-0"), KWARGS)
        self.mocker.result(0)
        self.check_call(ANY, KWARGS)  # rm from init dir
        self.mocker.call(self._without_sudo)

    def assert_pid_running(self, pid, expect):
        self.assertEquals(os.path.exists("/proc/%s" % pid), expect)

    def test_unit_name_with_path_manipulation_raises_assertion(self):
        self.assertRaises(
            AssertionError,
            UnitMachineDeployment,
            "../../etc/password/zebra/0",
            self.units_directory)

    def test_unit_directory(self):
        self.assertEqual(
            self.deployment.directory,
            os.path.join(self.units_directory,
                         self.unit_name.replace("/", "-")))

    def test_service_unit_start(self):
        """
        Starting a service unit will result in a unit workspace being created
        if it does not exist and a running service unit agent.
        """
        self.setup_mock()
        self.mock_install()
        self.mock_is_running(False)
        self.mock_start()
        self.mocker.replay()

        d = self.deployment.start(
            "123", get_test_zookeeper_address(), self.bundle)

        def verify_upstart(_):
            conf_path = os.path.join(self.init_dir, "juju-wordpress-0.conf")
            with open(conf_path) as f:
                lines = f.readlines()

            env = []
            for line in lines:
                if line.startswith("env "):
                    env.append(line[4:-1].split("=", 1))
                if line.startswith("exec "):
                    exec_ = line[5:-1]

            env = dict((k, v.strip('"')) for (k, v) in env)
            env.pop("PYTHONPATH")
            self.assertEquals(env, {
                "JUJU_HOME": self.juju_directory,
                "JUJU_UNIT_NAME": self.unit_name,
                "JUJU_ZOOKEEPER": get_test_zookeeper_address(),
                "JUJU_MACHINE_ID": "123"})

            log_file = os.path.join(
                self.deployment.directory, "charm.log")
            command = " ".join([
                "/usr/bin/python", "-m", "juju.agents.dummy", "--nodaemon",
                "--logfile", log_file, "--session-file",
                "/var/run/juju/unit-wordpress-0-agent.zksession",
                ">> /tmp/juju-wordpress-0.output 2>&1"])
            self.assertEquals(exec_, command)
        d.addCallback(verify_upstart)
        return d

    @inlineCallbacks
    def test_service_unit_destroy(self):
        """
        Forcibly stop a unit, and destroy any directories associated to it
        on the machine, and kills the unit agent process.
        """
        self.setup_mock()
        self.mock_install()
        self.mock_is_running(False)
        self.mock_start()
        self.mock_is_running(True)
        self.mock_destroy()
        self.mocker.replay()

        yield self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)

        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

        conf_path = os.path.join(self.init_dir, "juju-wordpress-0.conf")
        self.assertFalse(os.path.exists(conf_path))

    @inlineCallbacks
    def test_service_unit_destroy_undeployed(self):
        """
        If the unit is has not been deployed, nothing happens.
        """
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

    @inlineCallbacks
    def test_service_unit_destroy_not_running(self):
        """
        If the unit is not running, then destroy will just remove
        its directory.
        """
        self.setup_mock()
        self.mock_install()
        self.mock_is_running(False)
        self.mock_start()
        self.mock_is_running(False)
        self.check_call(ANY, KWARGS)  # rm from init dir
        self.mocker.call(self._without_sudo)
        self.mocker.replay()

        yield self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)
        yield self.deployment.destroy()
        self.assertFalse(os.path.exists(self.deployment.directory))

        conf_path = os.path.join(self.init_dir, "juju-wordpress-0.conf")
        self.assertFalse(os.path.exists(conf_path))

    def test_unpack_charm(self):
        """
        The deployment unpacks a charm bundle into the unit workspace.
        """
        self.deployment.unpack_charm(self.bundle)
        unit_path = os.path.join(
            self.units_directory, self.unit_name.replace("/", "-"))

        self.assertTrue(os.path.exists(unit_path))
        charm_path = os.path.join(unit_path, "charm")
        self.assertTrue(os.path.exists(charm_path))
        charm = get_charm_from_path(charm_path)
        self.assertEqual(
            charm.get_revision(), self.charm.get_revision())

    def test_unpack_charm_exception_invalid_charm(self):
        """
        If the charm bundle is corrupted or invalid a deployment specific
        error is raised.
        """
        error = self.assertRaises(
            UnitDeploymentError,
            self.deployment.unpack_charm, self.charm)
        self.assertEquals(
            str(error),
            "Invalid charm for deployment: %s" % self.charm.path)

    @inlineCallbacks
    def test_is_running_not_installed(self):
        """
        If there is no conf file the service unit is not running.
        """
        self.assertEqual((yield self.deployment.is_running()), False)

    @inlineCallbacks
    def test_is_running_not_running(self):
        """
        If the conf file exists, but job not running, unit not running
        """
        conf_path = os.path.join(self.init_dir, "juju-wordpress-0.conf")
        with open(conf_path, "w") as f:
            f.write("blah")
        self.setup_mock()
        self.mock_is_running(False)
        self.mocker.replay()
        self.assertEqual((yield self.deployment.is_running()), False)

    @inlineCallbacks
    def test_is_running_success(self):
        """
        Check running job.
        """
        conf_path = os.path.join(self.init_dir, "juju-wordpress-0.conf")
        with open(conf_path, "w") as f:
            f.write("blah")
        self.setup_mock()
        self.mock_is_running(True)
        self.mocker.replay()
        self.assertEqual((yield self.deployment.is_running()), True)

    @uses_sudo
    @inlineCallbacks
    def test_run_actual_process(self):
        # "unpatch" to use real /etc/init
        self.patch(UpstartService, "init_dir", self.real_init_dir)
        yield self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)
        old_pid = yield self.deployment.get_pid()
        self.assert_pid_running(old_pid, True)

        # Give the job a chance to fall over and be restarted (if the
        # pid doesn't change, that hasn't hapened)
        yield self.sleep(0.1)
        self.assertEquals((yield self.deployment.get_pid()), old_pid)
        self.assert_pid_running(old_pid, True)

        # Kick the job over ourselves; check it comes back
        os.system("sudo kill -9 %s" % old_pid)
        yield self.sleep(0.1)
        self.assert_pid_running(old_pid, False)
        new_pid = yield self.deployment.get_pid()
        self.assertNotEquals(new_pid, old_pid)
        self.assert_pid_running(new_pid, True)

        yield self.deployment.destroy()
        self.assertEquals((yield self.deployment.get_pid()), None)
        self.assert_pid_running(new_pid, False)

    @uses_sudo
    @inlineCallbacks
    def test_fail_to_run_actual_process(self):
        self.deployment.unit_agent_module = "haha.disregard.that"
        self.patch(UpstartService, "init_dir", self.real_init_dir)

        d = self.deployment.start(
            "0", get_test_zookeeper_address(), self.bundle)
        e = yield self.assertFailure(d, UnitDeploymentError)
        self.assertTrue(str(e).startswith(
            "Failed to start job juju-wordpress-0; got output:\n"))
        self.assertIn("No module named haha", str(e))

        yield self.deployment.destroy()