def test_add_multiple_units(self): """ 'juju add-unit <service_name>' will add a new service unit of the given service. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 1) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["add-unit", "--num-units", "5", "mysql"]) yield finished # verify the unit and its machine assignment. unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 6) topology = yield self.get_topology() unit = yield self.service_state1.get_unit_state("mysql/1") machine_id = topology.get_service_unit_machine( self.service_state1.internal_id, unit.internal_id) self.assertNotEqual(machine_id, None) for i in xrange(1, 6): self.assertIn( "Unit 'mysql/%d' added to service 'mysql'" % i, self.output.getvalue()) yield self.assert_machine_assignments("mysql", [1, 2, 3, 4, 5, 6])
def test_shell_with_unit(self): """ 'juju ssh mysql/0' will execute ssh against the machine hosting the unit. """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) mock_exec = self.mocker.replace(os.execvp) mock_exec("ssh", [ "ssh", "-o", "ControlPath " + self.tmp_home + "/.juju/ssh/master-%r@%h:%p", "-o", "ControlMaster no", "*****@*****.**" ]) # Track unwanted calls: calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.unit.connect_agent() main(["ssh", self.unit.unit_name]) yield finished self.assertEquals(calls, []) self.assertIn("Connecting to unit mysql/0 at mysql-0.example.com", self.output.getvalue())
def test_policy_from_environment(self): config = { "environments": {"firstenv": { "placement": "local", "type": "dummy"}}} self.write_config(dump(config)) self.config.load() ms0 = yield self.machine_state_manager.get_machine_state(0) yield self.service_unit1.unassign_from_machine() yield self.service_unit1.assign_to_machine(ms0) unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 1) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["add-unit", "mysql"]) yield finished # Verify the local policy was used topology = yield self.get_topology() unit = yield self.service_state1.get_unit_state("mysql/1") machine_id = topology.get_service_unit_machine( self.service_state1.internal_id, unit.internal_id) self.assertNotEqual(machine_id, None) self.assertIn( "Unit 'mysql/1' added to service 'mysql'", self.output.getvalue()) # adding a second unit still assigns to machine 0 with local policy yield self.assert_machine_assignments("mysql", [0, 0])
def test_scp_machine_id(self): """Verify scp command is invoked against the host for a machine ID.""" mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) # Verify expected call against scp mock_exec = self.mocker.replace(os.execvp) mock_exec( "scp", ["scp", "[email protected]:foo/*", "10.1.2.3:."]) # But no other calls calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.unit.connect_agent() main(["scp", "0:foo/*", "10.1.2.3:."]) yield finished self.assertEquals(calls, [])
def test_shell_with_machine_and_unconnected_machine_agent(self): """If a machine doesn't have a connected machine agent, the ssh command will wait till one exists before connecting. """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) mock_machine = self.mocker.patch(MachineState) mock_machine.watch_agent() self.mocker.result((succeed(False), succeed(True))) mock_exec = self.mocker.replace(os.execvp) mock_exec("ssh", [ "ssh", "-o", "ControlPath " + self.tmp_home + "/.juju/ssh/master-%r@%h:%p", "-o", "ControlMaster no", "*****@*****.**" ]) # Track unwanted calls: calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.machine.connect_agent() main(["ssh", "0"]) yield finished self.assertEquals(calls, []) self.assertIn("Waiting for machine to come up", self.output.getvalue())
def test_scp_machine_id(self): """Verify scp command is invoked against the host for a machine ID.""" # We need to do this because separate instances of DummyProvider don't # share instance state. mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) # Verify expected call against scp mock_exec = self.mocker.replace(os.execvp) mock_exec( "scp", ["scp", "[email protected]:foo/*", "10.1.2.3:."]) # But no other calls calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.unit.connect_agent() main(["scp", "0:foo/*", "10.1.2.3:."]) yield finished self.assertEquals(calls, [])
def test_open_tunnel(self): """ 'juju-control bootstrap' will invoke the bootstrap method of all configured machine providers in all environments. """ config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer"}}} self.write_config(dump(config)) self.setup_cli_reactor() self.setup_exit(0) provider = self.mocker.patch(MachineProvider) provider.connect(share=True) hanging_deferred = self.mocker.replace(open_tunnel.hanging_deferred) def callback(deferred): deferred.callback(None) return deferred hanging_deferred() self.mocker.passthrough(callback) self.mocker.replay() self.capture_stream("stderr") main(["open-tunnel"]) lines = filter(None, self.log.getvalue().split("\n")) self.assertEqual( lines, ["Tunnel to the environment is open. Press CTRL-C to close it.", "'open_tunnel' command finished successfully"])
def test_custom_parser_does_not_extend_to_subcommand(self): stderr = self.capture_stream("stderr") self.mocker.replay() try: main(["deploy"]) except SystemExit, e: self.assertEqual(e.args[0], 2)
def test_deploy_policy_from_environment(self): config = {"environments": {"firstenv": {"placement": "local", "type": "dummy", "default-series": "series"}}} self.write_config(yaml.dump(config)) self.config.load() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main( [ "deploy", "--environment", "firstenv", "--repository", self.unbundled_repo_path, "local:sample", "beekeeper", ] ) yield finished # and verify its placed on node 0 (as per local policy) service = yield self.service_state_manager.get_service_state("beekeeper") units = yield service.get_all_unit_states() unit = units[0] machine_id = yield unit.get_assigned_machine_id() self.assertEqual(machine_id, 0)
def test_deploy_multiple_environments_none_specified(self): """ If multiple environments are configured, with no default, one must be specified for the deploy command. """ self.capture_logging() self.setup_cli_reactor() self.setup_exit(1) self.mocker.replay() config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer" }, "secondenv": { "type": "dummy", "admin-secret": "marge" } } } self.write_config(yaml.dump(config)) stderr = self.capture_stream("stderr") main(["deploy", "--repository", self.unbundled_repo_path, "mysql"]) self.assertIn("There are multiple environments", stderr.getvalue())
def test_deploy_policy_from_environment(self): config = { "environments": { "firstenv": { "placement": "local", "type": "dummy", "default-series": "series" } } } self.write_config(yaml.dump(config)) self.config.load() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main([ "deploy", "--environment", "firstenv", "--repository", self.unbundled_repo_path, "local:sample", "beekeeper" ]) yield finished # and verify its placed on node 0 (as per local policy) service = yield self.service_state_manager.get_service_state( "beekeeper") units = yield service.get_all_unit_states() unit = units[0] machine_id = yield unit.get_assigned_machine_id() self.assertEqual(machine_id, 0)
def xtest_deploy_with_nonexistent_environment_specified(self): self.capture_logging() self.setup_cli_reactor() self.setup_exit(1) self.mocker.replay() config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer" }, "secondenv": { "type": "dummy", "admin-secret": "marge" } } } self.write_config(yaml.dump(config)) stderr = self.capture_stream("stderr") main([ "deploy", "--environment", "roman-candle", "--repository", self.unbundled_repo_path, "sample" ]) self.assertIn("Invalid environment 'roman-candle'", stderr.getvalue())
def test_include_log(self): """Messages can be filtered to include only certain log channels.""" log = yield self.get_configured_log("hook.output", "unit:cassandra/1") log2 = yield self.get_configured_log("unit.workflow", "unit:mysql/1") log3 = yield self.get_configured_log("provisioning", "agent:provision") for i in range(5): log.info(str(i)) for i in range(5): log2.info(str(i)) for i in range(5): log3.info(str(i)) cli_done = self.setup_cli_reactor() self.setup_exit() self.mocker.replay() stream = self.capture_stream("stdout") main(["debug-log", "--include", "unit.workflow", "-i", "agent:provision", "--limit", "8"]) yield cli_done output = stream.getvalue() self.assertNotIn("cassandra/1", output) self.assertIn("mysql/1", output) self.assertIn("provisioning", output)
def test_include_agent(self): """Messages can be filtered to include only certain agents.""" log = yield self.get_configured_log("hook.output", "unit:cassandra/10") log2 = yield self.get_configured_log("hook.output", "unit:cassandra/1") # example of an agent context name sans ":" log3 = yield self.get_configured_log("unit.workflow", "mysql/1") for i in range(5): log.info(str(i)) for i in range(5): log2.info(str(i)) for i in range(5): log3.info(str(i)) cli_done = self.setup_cli_reactor() self.setup_exit() self.mocker.replay() stream = self.capture_stream("stdout") main(["debug-log", "--include", "cassandra/1", "--limit", "4"]) yield cli_done output = stream.getvalue() self.assertNotIn("mysql/1", output) self.assertNotIn("cassandra/10", output) self.assertIn("cassandra/1", output)
def test_remove_unassigned_unit(self): """Remove unit also works if the unit is unassigned to a machine. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 3) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["remove-unit", "mysql/1"]) yield finished # verify the unit and its machine assignment. unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 2) topology = yield self.get_topology() topology = yield self.get_topology() self.assertFalse( topology.has_service_unit(self.service_state1.internal_id, self.service_unit2.internal_id)) topology = yield self.get_topology() self.assertTrue( topology.has_service_unit(self.service_state1.internal_id, self.service_unit1.internal_id))
def test_terminate_unused_machine(self): """Verify a typical allocation, unassignment, and then termination.""" wait_on_reactor_stopped = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() wordpress_service_state = \ yield self.add_service_from_charm("wordpress") wordpress_unit_state = yield wordpress_service_state.add_unit_state() wordpress_machine_state = \ yield self.machine_state_manager.add_machine_state() yield wordpress_unit_state.assign_to_machine(wordpress_machine_state) riak_service_state = yield self.add_service_from_charm("riak") riak_unit_state = yield riak_service_state.add_unit_state() riak_machine_state = \ yield self.machine_state_manager.add_machine_state() yield riak_unit_state.assign_to_machine(riak_machine_state) mysql_service_state = yield self.add_service_from_charm("mysql") mysql_unit_state = yield mysql_service_state.add_unit_state() mysql_machine_state = \ yield self.machine_state_manager.add_machine_state() yield mysql_unit_state.assign_to_machine(mysql_machine_state) yield wordpress_unit_state.unassign_from_machine() yield mysql_unit_state.unassign_from_machine() yield self.assert_machine_states([0, 1, 2, 3], []) main(["terminate-machine", "1", "3"]) yield wait_on_reactor_stopped self.assertIn( "Machines terminated: 1, 3", self.output.getvalue()) yield self.assert_machine_states([0, 2], [1, 3])
def test_remove_multiple_units(self): """ 'juju remove-unit <unit_name1> <unit_name2>...' removes desired units. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 3) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["remove-unit", "mysql/0", "mysql/2"]) yield finished topology = yield self.get_topology() self.assertFalse( topology.has_service_unit(self.service_state1.internal_id, self.service_unit1.internal_id)) topology = yield self.get_topology() self.assertTrue( topology.has_service_unit(self.service_state1.internal_id, self.service_unit2.internal_id)) self.assertFalse( topology.get_service_units_in_machine(self.machine.internal_id)) self.assertIn("Unit 'mysql/0' removed from service 'mysql'", self.output.getvalue()) self.assertIn("Unit 'mysql/2' removed from service 'mysql'", self.output.getvalue())
def test_passthrough_args(self): """Verify that args are passed through to the underlying scp command. For example, something like the following command should be valid:: $ juju scp -o "ConnectTimeout 60" foo mysql/0:/foo/bar """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) # Verify expected call against scp mock_exec = self.mocker.replace(os.execvp) mock_exec("scp", [ "scp", "-r", "-o", "ConnectTimeout 60", "foo", "[email protected]:/foo/bar" ]) # But no other calls calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() main([ "scp", "-r", "-o", "ConnectTimeout 60", "foo", "mysql/0:/foo/bar" ]) yield finished self.assertEquals(calls, [])
def test_passthrough_args(self): """Verify that args are passed through to the underlying scp command. For example, something like the following command should be valid:: $ juju scp -o "ConnectTimeout 60" foo mysql/0:/foo/bar """ # Verify expected call against scp mock_exec = self.mocker.replace(os.execvp) mock_exec("scp", [ "scp", "-r", "-o", "ConnectTimeout 60", "foo", "[email protected]:/foo/bar"]) # But no other calls calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() main(["scp", "-r", "-o", "ConnectTimeout 60", "foo", "mysql/0:/foo/bar"]) yield finished self.assertEquals(calls, [])
def test_shell_with_unit(self): """ 'juju ssh mysql/0' will execute ssh against the machine hosting the unit. """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) mock_exec = self.mocker.replace(os.execvp) mock_exec("ssh", [ "ssh", "-o", "ControlPath " + self.tmp_home + "/.juju/ssh/master-%r@%h:%p", "-o", "ControlMaster no", "*****@*****.**"]) # Track unwanted calls: calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.unit.connect_agent() main(["ssh", self.unit.unit_name]) yield finished self.assertEquals(calls, []) self.assertIn( "Connecting to unit mysql/0 at mysql-0.example.com", self.output.getvalue())
def test_destroy_environment(self): """Command will terminate instances in only one environment.""" config = { "environments": {"firstenv": {"type": "dummy"}, "secondenv": {"type": "dummy"}}} self.write_config(dump(config)) finished = self.setup_cli_reactor() envs = set(("firstenv", "secondenv")) def track_destroy_environment_call(self): envs.remove(self.environment_name) return succeed(True) provider = self.mocker.patch(MachineProvider) provider.destroy_environment() self.mocker.call(track_destroy_environment_call, with_object=True) self.setup_exit(0) mock_raw = self.mocker.replace(raw_input) mock_raw(MATCH(lambda x: x.startswith( "WARNING: this command will destroy the 'secondenv' " "environment (type: dummy)"))) self.mocker.result("y") self.mocker.replay() main(["destroy-environment", "-e", "secondenv"]) yield finished self.assertIn("Destroying environment 'secondenv' (type: dummy)...", self.log.getvalue()) self.assertEqual(envs, set(["firstenv"]))
def test_resolved_relation_already_running(self): """ 'juju resolved <unit_name> <rel_name>' will report if the relation is already running. """ service2 = yield self.service_state_manager.get_service_state( "wordpress") service_unit1 = yield service2.add_unit_state() service_relation = yield self.get_named_service_relation( service2, "db") yield self.setup_unit_relations( service_relation, (service_unit1, "up")) self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "wordpress/0", "db"]) yield finished self.assertIn("Matched relations are all running", self.output.getvalue()) self.assertEqual( (yield service_unit1.get_relation_resolved()), None)
def test_resolved_relation_some_already_resolved_conflict(self): """ 'juju resolved <service_name> <rel_name>' will mark resolved all down units that are not already marked resolved. """ service2 = yield self.service_state_manager.get_service_state( "wordpress") service_unit1 = yield service2.add_unit_state() service_relation = yield self.get_named_service_relation( service2, "db") yield self.setup_unit_relations(service_relation, (service_unit1, "down")) yield service_unit1.set_relation_resolved( {service_relation.internal_relation_id: NO_HOOKS}) self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "--retry", "wordpress/0", "db"]) yield finished self.assertEqual((yield service_unit1.get_relation_resolved()), {service_relation.internal_relation_id: NO_HOOKS}) self.assertIn( "Service unit 'wordpress/0' already has relations marked as resol", self.output.getvalue())
def test_relation_resolved(self): """ 'juju relation <unit_name> <rel_name>' will schedule the broken unit relations for being resolved. """ service_relation = yield self.get_named_service_relation( self.service1, "server") yield self.setup_unit_relations( service_relation, (self.service_unit1, "down"), (self.service_unit2, "up")) yield self.unit1_workflow.set_state("start_error") self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() self.assertEqual( (yield self.service_unit1.get_relation_resolved()), None) main(["resolved", "--retry", "mysql/0", service_relation.relation_name]) yield finished self.assertEqual( (yield self.service_unit1.get_relation_resolved()), {service_relation.internal_relation_id: RETRY_HOOKS}) self.assertEqual( (yield self.service_unit2.get_relation_resolved()), None) self.assertIn( "Marked unit 'mysql/0' relation 'server' as resolved", self.output.getvalue())
def test_resolved_already_running(self): """ 'juju resolved <unit_name>' will report if the unit is already running. """ # Just verify we don't accidentally mark up another unit of the service unit2_workflow = UnitWorkflowState( self.client, self.service_unit2, None, self.makeDir()) unit2_workflow.set_state("start_error") self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "mysql/0"]) yield finished self.assertEqual( (yield self.service_unit2.get_resolved()), None) self.assertEqual( (yield self.service_unit1.get_resolved()), None) self.assertNotIn( "Unit 'mysql/0 already running: started", self.output.getvalue())
def test_latest_local_leapfrog_dry_run(self): """Do nothing; log that local charm would be re-revisioned and used""" finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() metadata = self.charm.metadata.get_serialization_data() metadata["name"] = "mysql" repository = self.add_charm(metadata, 0) main(["upgrade-charm", "--dry-run", "--repository", repository.path, "mysql"]) yield finished charm_path = os.path.join(repository.path, "series", "mysql") self.assertIn( "%s would be set to revision 2" % charm_path, self.output.getvalue()) self.assertIn( "Service would be upgraded from charm 'local:series/mysql-1' to " "'local:series/mysql-2'", self.output.getvalue()) with open(os.path.join(charm_path, "revision")) as f: self.assertEquals(f.read(), "0") upgrade_flag = yield self.service_unit1.get_upgrade_flag() self.assertFalse(upgrade_flag)
def test_terminate_unused_machine(self): """Verify a typical allocation, unassignment, and then termination.""" wait_on_reactor_stopped = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() wordpress_service_state = \ yield self.add_service_from_charm("wordpress") wordpress_unit_state = yield wordpress_service_state.add_unit_state() wordpress_machine_state = yield self.add_machine_state() yield wordpress_unit_state.assign_to_machine(wordpress_machine_state) riak_service_state = yield self.add_service_from_charm("riak") riak_unit_state = yield riak_service_state.add_unit_state() riak_machine_state = yield self.add_machine_state() yield riak_unit_state.assign_to_machine(riak_machine_state) mysql_service_state = yield self.add_service_from_charm("mysql") mysql_unit_state = yield mysql_service_state.add_unit_state() mysql_machine_state = yield self.add_machine_state() yield mysql_unit_state.assign_to_machine(mysql_machine_state) yield wordpress_unit_state.unassign_from_machine() yield mysql_unit_state.unassign_from_machine() yield self.assert_machine_states([0, 1, 2, 3], []) # trash environment to check syncing yield self.client.delete("/environment") main(["terminate-machine", "1", "3"]) yield wait_on_reactor_stopped # check environment synced esm = EnvironmentStateManager(self.client) yield esm.get_config() self.assertIn( "Machines terminated: 1, 3", self.output.getvalue()) yield self.assert_machine_states([0, 2], [1, 3])
def test_add_unit(self): """ 'juju add-unit <service_name>' will add a new service unit of the given service. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 1) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() # trash environment to check syncing yield self.client.delete("/environment") main(["add-unit", "mysql"]) yield finished # verify the env state was synced esm = EnvironmentStateManager(self.client) yield esm.get_config() # verify the unit and its machine assignment. unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 2) topology = yield self.get_topology() unit = yield self.service_state1.get_unit_state("mysql/1") machine_id = topology.get_service_unit_machine(self.service_state1.internal_id, unit.internal_id) self.assertNotEqual(machine_id, None) self.assertIn("Unit 'mysql/1' added to service 'mysql'", self.output.getvalue()) yield self.assert_machine_assignments("mysql", [1, 2])
def test_add_multiple_units(self): """ 'juju add-unit <service_name>' will add a new service unit of the given service. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 1) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["add-unit", "--num-units", "5", "mysql"]) yield finished # verify the unit and its machine assignment. unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 6) topology = yield self.get_topology() unit = yield self.service_state1.get_unit_state("mysql/1") machine_id = topology.get_service_unit_machine(self.service_state1.internal_id, unit.internal_id) self.assertNotEqual(machine_id, None) for i in xrange(1, 6): self.assertIn("Unit 'mysql/%d' added to service 'mysql'" % i, self.output.getvalue()) yield self.assert_machine_assignments("mysql", [1, 2, 3, 4, 5, 6])
def test_log_object(self): """Messages that utilize string interpolation are rendered correctly. """ class Foobar(object): def __init__(self, v): self._v = v def __str__(self): return str("Foobar:%s" % self._v) log = yield self.get_configured_log("unit.workflow", "unit:mysql/1") log.info("found a %s", Foobar(21)) log.info("jack jumped into a %s", Foobar("cauldron")) cli_done = self.setup_cli_reactor() self.setup_exit() self.mocker.replay() stream = self.capture_stream("stdout") main(["debug-log", "--limit", "2"]) yield cli_done output = stream.getvalue() self.assertIn("Foobar:21", output) self.assertIn("Foobar:cauldron", output)
def test_policy_from_environment(self): config = {"environments": {"firstenv": {"placement": "local", "type": "dummy"}}} yield self.push_config("firstenv", config) ms0 = yield self.machine_state_manager.get_machine_state(0) yield self.service_unit1.unassign_from_machine() yield self.service_unit1.assign_to_machine(ms0) unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 1) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["add-unit", "mysql"]) yield finished # Verify the local policy was used topology = yield self.get_topology() unit = yield self.service_state1.get_unit_state("mysql/1") machine_id = topology.get_service_unit_machine(self.service_state1.internal_id, unit.internal_id) self.assertNotEqual(machine_id, None) self.assertIn("Unit 'mysql/1' added to service 'mysql'", self.output.getvalue()) # adding a second unit still assigns to machine 0 with local policy yield self.assert_machine_assignments("mysql", [0, 0])
def test_shell_with_machine_and_unconnected_machine_agent(self): """If a machine doesn't have a connected machine agent, the ssh command will wait till one exists before connecting. """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) mock_machine = self.mocker.patch(MachineState) mock_machine.watch_agent() self.mocker.result((succeed(False), succeed(True))) mock_exec = self.mocker.replace(os.execvp) mock_exec("ssh", [ "ssh", "-o", "ControlPath " + self.tmp_home + "/.juju/ssh/master-%r@%h:%p", "-o", "ControlMaster no", "*****@*****.**"]) # Track unwanted calls: calls = [] mock_exec(ARGS, KWARGS) self.mocker.count(0, None) self.mocker.call(lambda *args, **kwargs: calls.append((args, kwargs))) finished = self.setup_cli_reactor() self.mocker.replay() yield self.machine.connect_agent() main(["ssh", "0"]) yield finished self.assertEquals(calls, []) self.assertIn( "Waiting for machine to come up", self.output.getvalue())
def verify_hook_debug(self, *hook_names): """Utility function to verify hook debugging by name """ mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) system_mock = self.mocker.replace(os.system) system_mock(ANY) def on_ssh(command): self.assertStartsWith(command, "ssh -t [email protected]") self.assertEqual( (yield self.unit.get_hook_debug()), {"debug_hooks": list(hook_names)}) returnValue(True) self.mocker.call(on_ssh) finished = self.setup_cli_reactor() self.mocker.replay() yield self.unit.set_public_address("x11.example.com") args = ["debug-hooks", "mysql/0"] args.extend(hook_names) main(args) yield finished self.assertIn( "Connecting to remote machine", self.output.getvalue()) self.assertIn( "Debug session ended.", self.output.getvalue())
def test_debug_hook(self): """The debug cli will setup unit debug setting and ssh to a screen. """ system_mock = self.mocker.replace(os.system) system_mock(ANY) def on_ssh(command): self.assertStartsWith(command, "ssh -t [email protected]") # In the function, os.system yields to faciliate testing. self.assertEqual( (yield self.unit.get_hook_debug()), {"debug_hooks": ["*"]}) returnValue(True) self.mocker.call(on_ssh) finished = self.setup_cli_reactor() self.mocker.replay() # Setup the unit address. yield self.unit.set_public_address("x2.example.com") main(["debug-hooks", "mysql/0"]) yield finished self.assertIn( "Connecting to remote machine", self.output.getvalue()) self.assertIn( "Debug session ended.", self.output.getvalue())
def test_get_service_config(self): finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() self.service_state = yield self.add_service_from_charm("wordpress") config = yield self.service_state.get_config() # The value which isn't in the config won't be displayed. settings = {"blog-title": "Hello World", "world": 123} config.update(settings) yield config.write() output = self.capture_stream("stdout") main(["get", "wordpress"]) yield finished data = yaml.load(output.getvalue()) self.assertEqual( { "service": "wordpress", "charm": "local:series/wordpress-3", 'settings': { 'blog-title': { 'description': 'A descriptive title used for the blog.', 'type': 'string', 'value': 'Hello World' } } }, data)
def test_add_unit_unknown_service(self): finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["add-unit", "volcano"]) yield finished self.assertIn("Service 'volcano' was not found", self.stderr.getvalue())
def test_upgrade_charm_dryrun_reports_unupgradeable_units(self): """If there are units that won't be upgraded, dry-run will report them. """ repository = self.increment_charm(self.charm) service_unit2 = yield self.service_state1.add_unit_state() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["upgrade-charm", "-n", "--repository", repository.path, "mysql"]) yield finished # Verify dry run self.assertIn( "Service would be upgraded from charm", self.output.getvalue()) # Verify report of unupgradeable units self.assertIn( ("Unit 'mysql/1' is not in a running state " "(state: 'uninitialized'), won't upgrade"), self.output.getvalue()) # Verify no flags have been set. value = (yield service_unit2.get_upgrade_flag()) self.assertFalse(value) value = (yield self.service_unit1.get_upgrade_flag()) self.assertFalse(value)
def test_deploy_with_environment_specified(self): self.setup_cli_reactor() self.setup_exit(0) command = self.mocker.replace("juju.control.deploy.deploy") config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer"}, "secondenv": { "type": "dummy", "admin-secret": "marge"}}} self.write_config(yaml.dump(config)) def match_config(config): return isinstance(config, EnvironmentsConfig) def match_environment(environment): return isinstance(environment, Environment) and \ environment.name == "secondenv" command(MATCH(match_config), MATCH(match_environment), self.unbundled_repo_path, "local:sample", None, MATCH(lambda x: isinstance(x, logging.Logger)), ["cpu=36", "mem=64G"], None, False, num_units=1) self.mocker.replay() self.mocker.result(succeed(True)) main(["deploy", "--environment", "secondenv", "--repository", self.unbundled_repo_path, "--constraints", "cpu=36 mem=64G", "local:sample"])
def test_charm_upgrade(self): """ 'juju charm-upgrade <service_name>' will schedule a charm for upgrade. """ repository = self.increment_charm(self.charm) mock_environment = self.mocker.patch(Environment) mock_environment.get_machine_provider() self.mocker.result(self.provider) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["upgrade-charm", "--repository", repository.path, "mysql"]) yield finished # Verify the service has a new charm reference charm_id = yield self.service_state1.get_charm_id() self.assertEqual(charm_id, "local:series/mysql-2") # Verify the provider storage has been updated charm = yield repository.find(CharmURL.parse("local:series/mysql")) storage = self.provider.get_file_storage() try: yield storage.get( "local_3a_series_2f_mysql-2_3a_%s" % charm.get_sha256()) except FileNotFound: self.fail("New charm not uploaded") # Verify the upgrade flag on the service units. upgrade_flag = yield self.service_unit1.get_upgrade_flag() self.assertTrue(upgrade_flag)
def test_deploy_informs_with_subordinate(self): """Verify subordinate charm doesn't deploy. And that it properly notifies the user. """ log = self.capture_logging() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() # missing config file main(["deploy", "--repository", self.unbundled_repo_path, "local:logging"]) yield finished self.assertIn( "Subordinate 'logging' awaiting relationship to " "principal for deployment.\n", log.getvalue()) # and verify no units assigned to service service_state = yield self.service_state_manager.get_service_state("logging") self.assertEqual(service_state.service_name, "logging") units = yield service_state.get_unit_names() self.assertEqual(units, [])
def test_bootstrap(self): """ 'juju-control bootstrap' will invoke the bootstrap method of all configured machine providers in all environments. """ config = { "environments": { "firstenv": {"type": "dummy", "default-series": "homer"}, "secondenv": {"type": "dummy", "default-series": "marge"}, } } self.write_config(dump(config)) finished = self.setup_cli_reactor() self.setup_exit(0) provider = self.mocker.patch(MachineProvider) provider.bootstrap( {"ubuntu-series": "homer", "provider-type": "dummy", "arch": "arm", "cpu": 2.0, "mem": 512.0} ) self.mocker.result(succeed(True)) self.mocker.replay() self.capture_stream("stderr") main(["bootstrap", "-e", "firstenv", "--constraints", "arch=arm cpu=2"]) yield finished lines = filter(None, self.log.getvalue().split("\n")) self.assertEqual( lines, [ ("Bootstrapping environment 'firstenv' " "(origin: distro type: dummy)..."), "'bootstrap' command finished successfully", ], )
def test_deploy_legacy_keys_in_fresh_env(self): yield self.push_default_config() local_config = { "environments": {"firstenv": { "type": "dummy", "some-legacy-key": "blah", "default-series": "series"}}} self.write_config(yaml.dump(local_config)) self.config.load() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() stderr = self.capture_stream("stderr") main(["deploy", "--repository", self.unbundled_repo_path, "local:sample", "beekeeper"]) yield finished self.assertIn( "Your environments.yaml contains deprecated keys", stderr.getvalue()) service_manager = ServiceStateManager(self.client) yield self.assertFailure( service_manager.get_service_state("beekeeper"), ServiceStateNotFound)
def test_resolved_relation_some_already_resolved_conflict(self): """ 'juju resolved <service_name> <rel_name>' will mark resolved all down units that are not already marked resolved. """ service2 = yield self.service_state_manager.get_service_state( "wordpress") service_unit1 = yield service2.add_unit_state() service_relation = yield self.get_named_service_relation( service2, "db") yield self.setup_unit_relations( service_relation, (service_unit1, "down")) yield service_unit1.set_relation_resolved( {service_relation.internal_relation_id: NO_HOOKS}) self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "--retry", "wordpress/0", "db"]) yield finished self.assertEqual( (yield service_unit1.get_relation_resolved()), {service_relation.internal_relation_id: NO_HOOKS}) self.assertIn( "Service unit 'wordpress/0' already has relations marked as resol", self.output.getvalue())
def test_remove_multiple_units(self): """ 'juju remove-unit <unit_name1> <unit_name2>...' removes desired units. """ unit_names = yield self.service_state1.get_unit_names() self.assertEqual(len(unit_names), 3) finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["remove-unit", "mysql/0", "mysql/2"]) yield finished topology = yield self.get_topology() self.assertFalse(topology.has_service_unit( self.service_state1.internal_id, self.service_unit1.internal_id)) topology = yield self.get_topology() self.assertTrue(topology.has_service_unit( self.service_state1.internal_id, self.service_unit2.internal_id)) self.assertFalse( topology.get_service_units_in_machine(self.machine.internal_id)) self.assertIn( "Unit 'mysql/0' removed from service 'mysql'", self.output.getvalue()) self.assertIn( "Unit 'mysql/2' removed from service 'mysql'", self.output.getvalue())
def test_resolved_already_resolved(self): """ 'juju resolved <unit_name>' will report if the unit is already resolved. """ # Mark the unit as resolved and as in an error state. yield self.service_unit1.set_resolved(RETRY_HOOKS) yield self.unit1_workflow.set_state("start_error") unit2_workflow = UnitWorkflowState( self.client, self.service_unit1, None, self.makeDir()) unit2_workflow.set_state("start_error") self.assertEqual( (yield self.service_unit2.get_resolved()), None) self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "mysql/0"]) yield finished self.assertEqual( (yield self.service_unit1.get_resolved()), {"retry": RETRY_HOOKS}) self.assertNotIn( "Marked unit 'mysql/0' as resolved", self.output.getvalue()) self.assertIn( "Service unit 'mysql/0' is already marked as resolved.", self.stderr.getvalue(), "")
def test_get_service_config(self): finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() self.service_state = yield self.add_service_from_charm("wordpress") config = yield self.service_state.get_config() # The value which isn't in the config won't be displayed. settings = {"blog-title": "Hello World", "world": 123} config.update(settings) yield config.write() output = self.capture_stream("stdout") main(["get", "wordpress"]) yield finished data = yaml.load(output.getvalue()) self.assertEqual( {"service": "wordpress", "charm": "local:series/wordpress-3", 'settings': {'blog-title': { 'description': 'A descriptive title used for the blog.', 'type': 'string', 'value': 'Hello World'}}}, data)
def test_relation_resolved(self): """ 'juju relation <unit_name> <rel_name>' will schedule the broken unit relations for being resolved. """ service_relation = yield self.get_named_service_relation( self.service1, "server") yield self.setup_unit_relations(service_relation, (self.service_unit1, "down"), (self.service_unit2, "up")) yield self.unit1_workflow.set_state("start_error") self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() self.assertEqual((yield self.service_unit1.get_relation_resolved()), None) main( ["resolved", "--retry", "mysql/0", service_relation.relation_name]) yield finished self.assertEqual((yield self.service_unit1.get_relation_resolved()), {service_relation.internal_relation_id: RETRY_HOOKS}) self.assertEqual((yield self.service_unit2.get_relation_resolved()), None) self.assertIn("Marked unit 'mysql/0' relation 'server' as resolved", self.output.getvalue())
def test_upgrade_charm_with_unupgradeable_units(self): """If there are units that won't be upgraded, they will be reported, other units will be upgraded. """ repository = self.increment_charm(self.charm) service_unit2 = yield self.service_state1.add_unit_state() finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["upgrade-charm", "--repository", repository.path, "mysql"]) yield finished # Verify report of unupgradeable units self.assertIn( ("Unit 'mysql/1' is not in a running state " "(state: 'uninitialized'), won't upgrade"), self.output.getvalue()) # Verify flags only set on upgradeable unit. value = (yield service_unit2.get_upgrade_flag()) self.assertFalse(value) value = (yield self.service_unit1.get_upgrade_flag()) self.assertTrue(value)
def test_resolved_already_resolved(self): """ 'juju resolved <unit_name>' will report if the unit is already resolved. """ # Mark the unit as resolved and as in an error state. yield self.service_unit1.set_resolved(RETRY_HOOKS) yield self.unit1_workflow.set_state("start_error") unit2_workflow = UnitWorkflowState(self.client, self.service_unit1, None, self.makeDir()) unit2_workflow.set_state("start_error") self.assertEqual((yield self.service_unit2.get_resolved()), None) self.setup_exit(0) finished = self.setup_cli_reactor() self.mocker.replay() main(["resolved", "mysql/0"]) yield finished self.assertEqual((yield self.service_unit1.get_resolved()), {"retry": RETRY_HOOKS}) self.assertNotIn("Marked unit 'mysql/0' as resolved", self.output.getvalue()) self.assertIn("Service unit 'mysql/0' is already marked as resolved.", self.stderr.getvalue(), "")
def test_complex_filter(self): """Messages can be filtered to include only certain log channels.""" log = yield self.get_configured_log("hook.output", "unit:cassandra/1") log2 = yield self.get_configured_log("hook.output", "unit:cassandra/2") log3 = yield self.get_configured_log("hook.output", "unit:cassandra/3") for i in range(5): log.info(str(i)) for i in range(5): log2.info(str(i)) for i in range(5): log3.info(str(i)) cli_done = self.setup_cli_reactor() self.setup_exit() self.mocker.replay() stream = self.capture_stream("stdout") main( ["debug-log", "-i", "cassandra/*", "-x", "cassandra/1", "-n", "8"]) yield cli_done output = stream.getvalue() self.assertNotIn("cassandra/1", output) self.assertIn("cassandra/2", output) self.assertIn("cassandra/3", output)
def test_upgrade_charm_unknown_service(self): finished = self.setup_cli_reactor() self.setup_exit(0) self.mocker.replay() main(["upgrade-charm", "--repository", self.makeDir(), "volcano"]) yield finished self.assertIn( "Service 'volcano' was not found", self.stderr.getvalue())
def test_unexpose_unknown_service(self): """Test subcommand fails if service does not exist.""" finished = self.setup_cli_reactor() self.setup_exit(0) # XXX change when bug 697093 is fixed self.mocker.replay() main(["unexpose", "foobar"]) yield finished self.assertIn("Service 'foobar' was not found", self.stderr.getvalue())