class UpstartServiceTest(TestCase): @inlineCallbacks def setUp(self): yield super(UpstartServiceTest, self).setUp() self.init_dir = self.makeDir() self.conf = os.path.join(self.init_dir, "some-name.conf") self.output = "/tmp/some-name.output" self.patch(UpstartService, "init_dir", self.init_dir) self.service = UpstartService("some-name") def setup_service(self): self.service.set_description("a wretched hive of scum and villainy") self.service.set_command("/bin/imagination-failure --no-ideas") self.service.set_environ({"LIGHTSABER": "civilised weapon"}) def setup_mock(self): self.check_call = self.mocker.replace("subprocess.check_call") self.getProcessOutput = self.mocker.replace( "twisted.internet.utils.getProcessOutput") def mock_status(self, result): self.getProcessOutput("/sbin/status", ["some-name"]) self.mocker.result(result) def mock_call(self, args, output=None): self.check_call(args, KWARGS) if output is None: self.mocker.result(0) else: def write(ANY, **_): with open(self.output, "w") as f: f.write(output) self.mocker.call(write) def mock_start(self, output=None): self.mock_call(("/sbin/start", "some-name"), output) def mock_stop(self): self.mock_call(("/sbin/stop", "some-name")) def mock_check_success(self): for _ in range(5): self.mock_status(succeed("blah start/running blah 12345")) def mock_check_unstable(self): for _ in range(4): self.mock_status(succeed("blah start/running blah 12345")) self.mock_status(succeed("blah start/running blah 12346")) def mock_check_not_running(self): self.mock_status(succeed("blah")) def write_dummy_conf(self): with open(self.conf, "w") as f: f.write("dummy") def assert_dummy_conf(self): with open(self.conf) as f: self.assertEquals(f.read(), "dummy") def assert_no_conf(self): self.assertFalse(os.path.exists(self.conf)) def assert_conf(self, name="test_standard_install"): with open(os.path.join(DATA_DIR, name)) as expected: with open(self.conf) as actual: self.assertEquals(actual.read(), expected.read()) def test_is_installed(self): """Check is_installed depends on conf file existence""" self.assertFalse(self.service.is_installed()) self.write_dummy_conf() self.assertTrue(self.service.is_installed()) def test_init_dir(self): """ Check is_installed still works when init_dir specified explicitly """ self.patch(UpstartService, "init_dir", "/BAD/PATH") self.service = UpstartService("some-name", init_dir=self.init_dir) self.setup_service() self.assertFalse(self.service.is_installed()) self.write_dummy_conf() self.assertTrue(self.service.is_installed()) @inlineCallbacks def test_is_running(self): """ Check is_running interprets status output (when service is installed) """ self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_status(succeed("blah blob/gibbering blah")) self.mock_status(succeed("blah start/running blah 12345")) self.mocker.replay() # Won't hit status; conf is not installed self.assertFalse((yield self.service.is_running())) self.write_dummy_conf() # These 3 calls correspond to the first 3 mock_status calls above self.assertFalse((yield self.service.is_running())) self.assertFalse((yield self.service.is_running())) self.assertTrue((yield self.service.is_running())) @inlineCallbacks def test_is_stable_yes(self): self.setup_mock() self.mock_check_success() self.mocker.replay() self.write_dummy_conf() self.assertTrue((yield self.service.is_stable())) @inlineCallbacks def test_is_stable_no(self): self.setup_mock() self.mock_check_unstable() self.mocker.replay() self.write_dummy_conf() self.assertFalse((yield self.service.is_stable())) @inlineCallbacks def test_is_stable_not_running(self): self.setup_mock() self.mock_check_not_running() self.mocker.replay() self.write_dummy_conf() self.assertFalse((yield self.service.is_stable())) @inlineCallbacks def test_is_stable_not_even_installed(self): self.assertFalse((yield self.service.is_stable())) @inlineCallbacks def test_get_pid(self): """ Check get_pid interprets status output (when service is installed) """ self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_status(succeed("blah blob/gibbering blah")) self.mock_status(succeed("blah start/running blah 12345")) self.mocker.replay() # Won't hit status; conf is not installed self.assertEquals((yield self.service.get_pid()), None) self.write_dummy_conf() # These 3 calls correspond to the first 3 mock_status calls above self.assertEquals((yield self.service.get_pid()), None) self.assertEquals((yield self.service.get_pid()), None) self.assertEquals((yield self.service.get_pid()), 12345) @inlineCallbacks def test_basic_install(self): """Check a simple UpstartService writes expected conf file""" e = yield self.assertFailure(self.service.install(), ServiceError) self.assertEquals(str(e), "Cannot render .conf: no description set") self.service.set_description("uninteresting service") e = yield self.assertFailure(self.service.install(), ServiceError) self.assertEquals(str(e), "Cannot render .conf: no command set") self.service.set_command("/bin/false") yield self.service.install() self.assert_conf("test_basic_install") @inlineCallbacks def test_less_basic_install(self): """Check conf for a different UpstartService (which sets an env var)""" self.service.set_description("pew pew pew blam") self.service.set_command("/bin/deathstar --ignore-ewoks endor") self.service.set_environ({"FOO": "bar baz qux", "PEW": "pew"}) self.service.set_output_path("/somewhere/else") yield self.service.install() self.assert_conf("test_less_basic_install") def test_install_via_script(self): """Check that the output-as-script form does the right thing""" self.setup_service() install, start = self.service.get_cloud_init_commands() os.system(install) self.assert_conf() self.assertEquals(start, "/sbin/start some-name") @inlineCallbacks def test_start_not_installed(self): """Check that .start() also installs if necessary""" self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_start() self.mock_check_success() self.mocker.replay() self.setup_service() yield self.service.start() self.assert_conf() @inlineCallbacks def test_start_not_started_stable(self): """Check that .start() starts if stopped, and checks for stable pid""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_start("ignored") self.mock_check_success() self.mocker.replay() self.setup_service() yield self.service.start() self.assert_dummy_conf() @inlineCallbacks def test_start_not_started_unstable(self): """Check that .start() starts if stopped, and raises on unstable pid""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_start("kangaroo") self.mock_check_unstable() self.mocker.replay() self.setup_service() e = yield self.assertFailure(self.service.start(), ServiceError) self.assertEquals( str(e), "Failed to start job some-name; got output:\nkangaroo") self.assert_dummy_conf() @inlineCallbacks def test_start_not_started_failure(self): """Check that .start() starts if stopped, and raises on no pid""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mock_start() self.mock_check_not_running() self.mocker.replay() self.setup_service() e = yield self.assertFailure(self.service.start(), ServiceError) self.assertEquals( str(e), "Failed to start job some-name; no output detected") self.assert_dummy_conf() @inlineCallbacks def test_start_started(self): """Check that .start() does nothing if already running""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah start/running blah 12345")) self.mocker.replay() self.setup_service() yield self.service.start() self.assert_dummy_conf() @inlineCallbacks def test_destroy_not_installed(self): """Check .destroy() does nothing if not installed""" yield self.service.destroy() self.assert_no_conf() @inlineCallbacks def test_destroy_not_started(self): """Check .destroy just deletes conf if not running""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah stop/waiting blah")) self.mocker.replay() yield self.service.destroy() self.assert_no_conf() @inlineCallbacks def test_destroy_started(self): """Check .destroy() stops running service and deletes conf file""" self.write_dummy_conf() self.setup_mock() self.mock_status(succeed("blah start/running blah 54321")) self.mock_stop() self.mocker.replay() yield self.service.destroy() self.assert_no_conf() @inlineCallbacks def test_use_sudo(self): """Check that expected commands are generated when use_sudo is set""" self.setup_mock() self.service = UpstartService("some-name", use_sudo=True) self.setup_service() with open(self.output, "w") as f: f.write("clear this file out...") def verify_cp(args, **kwargs): sudo, cp, src, dst = args self.assertEquals(sudo, "sudo") self.assertEquals(cp, "cp") with open(os.path.join(DATA_DIR, "test_standard_install")) as exp: with open(src) as actual: self.assertEquals(actual.read(), exp.read()) self.assertEquals(dst, self.conf) self.write_dummy_conf() self.check_call(ANY, KWARGS) self.mocker.call(verify_cp) self.mock_call(("sudo", "rm", self.output)) self.mock_call(("sudo", "chmod", "644", self.conf)) self.mock_status(succeed("blah stop/waiting blah")) self.mock_call(("sudo", "/sbin/start", "some-name")) # 5 for initial stability check; 1 for final do-we-need-to-stop check for _ in range(6): self.mock_status(succeed("blah start/running blah 12345")) self.mock_call(("sudo", "/sbin/stop", "some-name")) self.mock_call(("sudo", "rm", self.conf)) self.mock_call(("sudo", "rm", self.output)) self.mocker.replay() yield self.service.start() yield self.service.destroy()
def start(self, machine_id, zookeeper_hosts, bundle): """Start the unit. Creates and starts an lxc container for the unit. """ # remove any quoting around the key public_key = os.environ.get("JUJU_PUBLIC_KEY", "") public_key = public_key.strip("'\"") # Build a template container that can be cloned in deploy # we leave the loosely initialized self.container in place for # the class as thats all we need for methods other than start. self.container, self.directory = yield self._get_container( machine_id, bundle, public_key) # Create state directories for unit in the container self.setup_directories() # Extract the charm bundle charm_path = os.path.join( self.directory, "var", "lib", "juju", "units", self.unit_path_name, "charm") bundle.extract_to(charm_path) log.debug("Charm extracted into container") # Write upstart file for the agent into the container service_name = "juju-%s" % self.unit_path_name init_dir = os.path.join(self.directory, "etc", "init") service = UpstartService(service_name, init_dir=init_dir) service.set_description( "Juju unit agent for %s" % self.unit_name) service.set_environ(_get_environment( self.unit_name, "/var/lib/juju", machine_id, zookeeper_hosts)) service.set_output_path( "/var/log/juju/unit-%s-output.log" % self.unit_path_name) service.set_command(" ".join(( "/usr/bin/python", "-m", "juju.agents.unit", "--nodaemon", "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name, "--session-file", "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) yield service.install() # Create symlinks on the host for easier access to the unit log files unit_log_path_host = os.path.join( self.juju_home, "units", self.unit_path_name, "unit.log") if not os.path.lexists(unit_log_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s.log" % self.unit_path_name), unit_log_path_host) unit_output_path_host = os.path.join( self.juju_home, "units", self.unit_path_name, "output.log") if not os.path.lexists(unit_output_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s-output.log" % self.unit_path_name), unit_output_path_host) # Debug log for the container container_log_path = os.path.join( self.juju_home, "units", self.unit_path_name, "container.log") self.container.debug_log = container_log_path log.debug("Starting container...") yield self.container.run() log.info("Started container for %s", self.unit_name)