class UnitDeployerTest(StateTestBase): @inlineCallbacks def setUp(self): yield super(UnitDeployerTest, self).setUp() self.output = self.capture_logging(level=logging.DEBUG) yield self.push_default_config() # Load the environment with the charm state and charm binary environment = self.config.get_default() provider = environment.get_machine_provider() storage = provider.get_file_storage() publisher = CharmPublisher(self.client, storage) yield publisher.add_charm(local_charm_id(self.charm), self.charm) self.charm_state, = yield publisher.publish() # Create a service from the charm, then add a unit and assign # it to a machine. self.service_state_manager = ServiceStateManager(self.client) self.machine_state_manager = MachineStateManager(self.client) self.service = yield self.service_state_manager.add_service_state( "myblog", self.charm_state, dummy_constraints) self.unit_state = yield self.service.add_unit_state() self.machine_state = yield self.machine_state_manager.\ add_machine_state(series_constraints) yield self.unit_state.assign_to_machine(self.machine_state) # NOTE machine_id must be a str to use with one of the # deployment classes self.juju_dir = self.makeDir() self.unit_manager = UnitDeployer( self.client, str(self.machine_state.id), self.juju_dir) yield self.unit_manager.start() def test_start_initializes(self): """Verify starting unit manager initializes any necessary resources.""" self.assertTrue(os.path.isdir(self.unit_manager.charms_directory)) self.assertEqual(self.unit_manager.deploy_factory, UnitMachineDeployment) @inlineCallbacks def test_charm_download(self): """Downloading a charm should store the charm locally.""" yield self.unit_manager.download_charm(self.charm_state) checksum = self.charm.get_sha256() charm_id = local_charm_id(self.charm) charm_key = under.quote("%s:%s" % (charm_id, checksum)) charm_path = os.path.join(self.unit_manager.charms_directory, charm_key) self.assertTrue(os.path.exists(charm_path)) bundle = CharmBundle(charm_path) self.assertEquals( bundle.get_revision(), self.charm.get_revision()) self.assertEquals(bundle.get_sha256(), checksum) self.assertIn( "Downloading charm %s" % charm_id, self.output.getvalue()) @inlineCallbacks def test_start_service_unit(self): """Verify starting a service unit kicks off the desired deployment.""" mock_deployment = self.mocker.patch(self.unit_manager.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) test_deferred = Deferred() def test_complete(machine_id, servers, bundle): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() yield self.unit_manager.start_service_unit("myblog/0") yield test_deferred self.assertLogLines( self.output.getvalue(), ["Downloading charm local:series/dummy-1 to %s" % \ os.path.join(self.juju_dir, "charms"), "Starting service unit myblog/0...", "Started service unit myblog/0"]) @inlineCallbacks def test_kill_service_unit(self): """Verify killing a service unit destroys the deployment.""" mock_deployment = self.mocker.patch(self.unit_manager.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) self.mocker.result(succeed(True)) mock_deployment.destroy() self.mocker.result(succeed(True)) test_deferred = Deferred() def test_complete(): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() # Start yield self.unit_manager.start_service_unit("myblog/0") # and stop. yield self.unit_state.unassign_from_machine() yield self.unit_manager.kill_service_unit("myblog/0") yield test_deferred self.assertLogLines( self.output.getvalue(), ["Downloading charm local:series/dummy-1 to %s" % \ os.path.join(self.juju_dir, "charms"), "Starting service unit myblog/0...", "Started service unit myblog/0", "Stopping service unit myblog/0...", "Stopped service unit myblog/0"])
def deploy(env_config, environment, repository_path, charm_name, service_name, log, config_file=None, num_units=1): """Deploy a charm within an environment. This will publish the charm to the environment, creating a service from the charm, and get it set to be launched on a new machine. """ repo, charm_url = resolve( charm_name, repository_path, environment.default_series) # Validate config options prior to deployment attempt service_options = {} service_name = service_name or charm_url.name if config_file: service_options = parse_config_options(config_file, service_name) charm = yield repo.find(charm_url) charm_id = str(charm_url.with_revision(charm.get_revision())) provider = environment.get_machine_provider() placement_policy = provider.get_placement_policy() client = yield provider.connect() try: storage = yield provider.get_file_storage() service_manager = ServiceStateManager(client) environment_state_manager = EnvironmentStateManager(client) yield environment_state_manager.set_config_state( env_config, environment.name) # Publish the charm to juju publisher = CharmPublisher(client, storage) yield publisher.add_charm(charm_id, charm) result = yield publisher.publish() # In future we might have multiple charms be published at # the same time. For now, extract the charm_state from the # list. charm_state = result[0] # Create the service state service_state = yield service_manager.add_service_state( service_name, charm_state) # Use the charm's ConfigOptions instance to validate service # options.. Invalid options passed will thrown an exception # and prevent the deploy. state = yield service_state.get_config() charm_config = yield charm_state.get_config() # return the validated options with the defaults included service_options = charm_config.validate(service_options) state.update(service_options) yield state.write() # Create desired number of service units for i in xrange(num_units): unit_state = yield service_state.add_unit_state() yield place_unit(client, placement_policy, unit_state) # Check if we have any peer relations to establish if charm.metadata.peers: relation_manager = RelationStateManager(client) for peer_name, peer_info in charm.metadata.peers.items(): yield relation_manager.add_relation_state( RelationEndpoint(service_name, peer_info["interface"], peer_name, "peer")) log.info("Charm deployed as service: %r", service_name) finally: yield client.close()
def deploy(env_config, environment, repository_path, charm_name, service_name, log, constraint_strs, config_file=None, upgrade=False, num_units=1): """Deploy a charm within an environment. This will publish the charm to the environment, creating a service from the charm, and get it set to be launched on a new machine. If --repository is not specified, it will be taken from the environment variable JUJU_REPOSITORY. """ repo, charm_url = resolve( charm_name, repository_path, environment.default_series) log.info("Searching for charm %s in %s" % (charm_url, repo)) charm = yield repo.find(charm_url) if upgrade: if repo.type != "local" or charm.type != "dir": raise CharmError( charm.path, "Only local directory charms can be upgraded on deploy") charm.set_revision(charm.get_revision() + 1) charm_id = str(charm_url.with_revision(charm.get_revision())) # Validate config options prior to deployment attempt service_options = {} service_name = service_name or charm_url.name if config_file: service_options = parse_config_options( config_file, service_name, charm) charm = yield repo.find(charm_url) charm_id = str(charm_url.with_revision(charm.get_revision())) provider = environment.get_machine_provider() placement_policy = provider.get_placement_policy() constraint_set = yield provider.get_constraint_set() constraints = constraint_set.parse(constraint_strs) client = yield provider.connect() try: yield legacy.check_constraints(client, constraint_strs) yield legacy.check_environment( client, provider.get_legacy_config_keys()) yield sync_environment_state(client, env_config, environment.name) # Publish the charm to juju storage = yield provider.get_file_storage() publisher = CharmPublisher(client, storage) yield publisher.add_charm(charm_id, charm) result = yield publisher.publish() # In future we might have multiple charms be published at # the same time. For now, extract the charm_state from the # list. charm_state = result[0] # Create the service state service_manager = ServiceStateManager(client) service_state = yield service_manager.add_service_state( service_name, charm_state, constraints) # Use the charm's ConfigOptions instance to validate service # options.. Invalid options passed will thrown an exception # and prevent the deploy. state = yield service_state.get_config() charm_config = yield charm_state.get_config() # return the validated options with the defaults included service_options = charm_config.validate(service_options) state.update(service_options) yield state.write() # Create desired number of service units if (yield service_state.is_subordinate()): log.info("Subordinate %r awaiting relationship " "to principal for deployment.", service_name) else: for i in xrange(num_units): unit_state = yield service_state.add_unit_state() yield place_unit(client, placement_policy, unit_state) # Check if we have any peer relations to establish if charm.metadata.peers: relation_manager = RelationStateManager(client) for peer_name, peer_info in charm.metadata.peers.items(): yield relation_manager.add_relation_state( RelationEndpoint(service_name, peer_info["interface"], peer_name, "peer")) log.info("Charm deployed as service: %r", service_name) finally: yield client.close()
class MachineStateManagerTest(StateTestBase): @inlineCallbacks def setUp(self): yield super(MachineStateManagerTest, self).setUp() self.charm_state_manager = CharmStateManager(self.client) self.machine_state_manager = MachineStateManager(self.client) self.service_state_manager = ServiceStateManager(self.client) self.charm_state = yield self.charm_state_manager.add_charm_state( local_charm_id(self.charm), self.charm, "") @inlineCallbacks def add_service(self, service_name): service_state = yield self.service_state_manager.add_service_state( service_name, self.charm_state) returnValue(service_state) @inlineCallbacks def test_add_machine(self): """ Adding a machine state should register it in zookeeper. """ machine_state1 = yield self.machine_state_manager.add_machine_state() machine_state2 = yield self.machine_state_manager.add_machine_state() self.assertEquals(machine_state1.id, 0) self.assertEquals(machine_state1.internal_id, "machine-0000000000") self.assertEquals(machine_state2.id, 1) self.assertEquals(machine_state2.internal_id, "machine-0000000001") children = yield self.client.get_children("/machines") self.assertEquals(sorted(children), ["machine-0000000000", "machine-0000000001"]) topology = yield self.get_topology() self.assertTrue(topology.has_machine("machine-0000000000")) self.assertTrue(topology.has_machine("machine-0000000001")) @inlineCallbacks def test_machine_str_representation(self): """The str(machine) value includes the machine id. """ machine_state1 = yield self.machine_state_manager.add_machine_state() self.assertEqual( str(machine_state1), "<MachineState id:machine-%010d>" % (0)) @inlineCallbacks def test_remove_machine(self): """ Adding a machine state should register it in zookeeper. """ machine_state1 = yield self.machine_state_manager.add_machine_state() yield self.machine_state_manager.add_machine_state() removed = yield self.machine_state_manager.remove_machine_state( machine_state1.id) self.assertTrue(removed) children = yield self.client.get_children("/machines") self.assertEquals(sorted(children), ["machine-0000000001"]) topology = yield self.get_topology() self.assertFalse(topology.has_machine("machine-0000000000")) self.assertTrue(topology.has_machine("machine-0000000001")) # Removing a non-existing machine again won't fail, since the end # intention is preserved. This makes dealing with concurrency easier. # However, False will be returned in this case. removed = yield self.machine_state_manager.remove_machine_state( machine_state1.id) self.assertFalse(removed) @inlineCallbacks def test_remove_machine_with_agent(self): """Removing a machine with a connected machine agent should succeed. The removal signals intent to remove a working machine (with an agent) with the provisioning agent to remove it subsequently. """ # Add two machines. machine_state1 = yield self.machine_state_manager.add_machine_state() yield self.machine_state_manager.add_machine_state() # Connect an agent yield machine_state1.connect_agent() # Remove a machine removed = yield self.machine_state_manager.remove_machine_state( machine_state1.id) self.assertTrue(removed) # Verify the second one is still present children = yield self.client.get_children("/machines") self.assertEquals(sorted(children), ["machine-0000000001"]) # Verify the topology state. topology = yield self.get_topology() self.assertFalse(topology.has_machine("machine-0000000000")) self.assertTrue(topology.has_machine("machine-0000000001")) @inlineCallbacks def test_get_machine_and_check_attributes(self): """ Getting a machine state should be possible using both the user-oriented id and the internal id. """ yield self.machine_state_manager.add_machine_state() yield self.machine_state_manager.add_machine_state() machine_state = yield self.machine_state_manager.get_machine_state(0) self.assertEquals(machine_state.id, 0) machine_state = yield self.machine_state_manager.get_machine_state("0") self.assertEquals(machine_state.id, 0) yield self.assertFailure( self.machine_state_manager.get_machine_state("a"), MachineStateNotFound) @inlineCallbacks def test_get_machine_not_found(self): """ Getting a machine state which is not available should errback a meaningful error. """ # No state whatsoever. try: yield self.machine_state_manager.get_machine_state(0) except MachineStateNotFound, e: self.assertEquals(e.machine_id, 0) else:
class MachineAgentTest(AgentTestBase, RepositoryTestBase): agent_class = MachineAgent @inlineCallbacks def setUp(self): yield super(MachineAgentTest, self).setUp() self.output = self.capture_logging(level=logging.DEBUG) environment = self.config.get_default() # Load the environment with the charm state and charm binary self.provider = environment.get_machine_provider() self.storage = self.provider.get_file_storage() self.charm = CharmDirectory(self.sample_dir1) self.publisher = CharmPublisher(self.client, self.storage) yield self.publisher.add_charm(local_charm_id(self.charm), self.charm) charm_states = yield self.publisher.publish() self.charm_state = charm_states[0] # Create a service from the charm from which we can create units for # the machine. self.service_state_manager = ServiceStateManager(self.client) self.service = yield self.service_state_manager.add_service_state( "fatality-blog", self.charm_state, dummy_constraints) @inlineCallbacks def get_agent_config(self): # gets invoked by AgentTestBase.setUp options = yield super(MachineAgentTest, self).get_agent_config() machine_state_manager = MachineStateManager(self.client) self.machine_state = yield machine_state_manager.add_machine_state( series_constraints) self.change_environment( JUJU_MACHINE_ID="0", JUJU_HOME=self.juju_directory) options["machine_id"] = str(self.machine_state.id) # Start the agent with watching enabled returnValue(options) @inlineCallbacks def test_start_begins_watch_and_initializes_directories(self): self.agent.set_watch_enabled(True) mock_machine_state = self.mocker.patch(MachineState) mock_machine_state.watch_assigned_units( self.agent.watch_service_units) self.mocker.replay() yield self.agent.startService() self.assertTrue(os.path.isdir(self.agent.units_directory)) self.assertTrue(os.path.isdir(self.agent.unit_state_directory)) self.assertIn( "Machine agent started id:%s" % self.agent.get_machine_id(), self.output.getvalue()) yield self.agent.stopService() def test_agent_machine_id_environment_extraction(self): self.change_args("es-agent") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual( config["machine_id"], "0") def test_get_agent_name(self): self.assertEqual(self.agent.get_agent_name(), "Machine:0") def test_agent_machine_id_cli_error(self): """ If the machine id can't be found, a detailed error message is given. """ # initially setup by get_agent_config in setUp self.change_environment(JUJU_MACHINE_ID="") self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address(), "--juju-directory", self.makeDir(), "--session-file", self.makeFile()) parser = argparse.ArgumentParser() self.agent.setup_options(parser) options = parser.parse_args(namespace=TwistedOptionNamespace()) e = self.assertRaises( JujuError, self.agent.configure, options) self.assertIn( ("--machine-id must be provided in the command line," " or $JUJU_MACHINE_ID in the environment"), str(e)) def test_agent_machine_id_cli_extraction(self): """Command line passing of machine id works and has precedence over environment arg passing.""" self.change_environment(JUJU_MACHINE_ID=str(21)) self.change_args("es-agent", "--machine-id", "0") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual( config["machine_id"], "0") def test_machine_agent_knows_its_machine_id(self): self.assertEqual(self.agent.get_machine_id(), "0") @inlineCallbacks def test_watch_new_service_unit(self): """ Adding a new service unit is detected by the watch. """ from juju.unit.deploy import UnitDeployer mock_deployer = self.mocker.patch(UnitDeployer) mock_deployer.start_service_unit("fatality-blog/0") test_deferred = Deferred() def test_complete(service_name): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() self.agent.set_watch_enabled(True) yield self.agent.startService() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) yield test_deferred self.assertIn( "Units changed old:set([]) new:set(['fatality-blog/0'])", self.output.getvalue()) def test_watch_new_service_unit_error(self): """ An error while starting a new service is logged """ # Inject an error into the service deployment from juju.unit.deploy import UnitDeployer mock_deployer = self.mocker.patch(UnitDeployer) mock_deployer.start_service_unit("fatality-blog/0") self.mocker.result(fail(SyntaxError("Bad"))) self.mocker.replay() yield self.agent.startService() yield self.agent.watch_service_units(None, set(["fatality-blog/0"])) self.assertIn("Starting service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error starting unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("SyntaxError: Bad", self.output.getvalue()) @inlineCallbacks def test_service_unit_removed(self): """ Service unit removed with manual invocation of watch_service_units. """ from juju.unit.deploy import UnitDeployer mock_deployer = self.mocker.patch(UnitDeployer) started = Deferred() mock_deployer.start_service_unit("fatality-blog/0") self.mocker.call(started.callback) stopped = Deferred() mock_deployer.kill_service_unit("fatality-blog/0") self.mocker.call(stopped.callback) self.mocker.replay() # Start the agent with watching enabled self.agent.set_watch_enabled(True) yield self.agent.startService() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) # Need to ensure no there's no concurrency creating an overlap # between assigning, unassigning to machine, since it is # possible then for the watch in the machine agent to not # observe *any* change in this case ("you cannot reliably see # every change that happens to a node in ZooKeeper") yield started # And now remove it yield self.service_unit.unassign_from_machine() yield stopped @inlineCallbacks def test_watch_removed_service_unit_error(self): """ An error while removing a service unit is logged """ from juju.unit.deploy import UnitDeployer mock_deployer = self.mocker.patch(UnitDeployer) mock_deployer.kill_service_unit("fatality-blog/0") self.mocker.result(fail(OSError("Bad"))) self.mocker.replay() yield self.agent.startService() yield self.agent.watch_service_units(set(["fatality-blog/0"]), set()) self.assertIn("Stopping service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error stopping unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("OSError: Bad", self.output.getvalue())
class MachineAgentTest(AgentTestBase, RepositoryTestBase): agent_class = MachineAgent @inlineCallbacks def setUp(self): yield super(MachineAgentTest, self).setUp() self.output = self.capture_logging( "juju.agents.machine", level=logging.DEBUG) config = self.get_test_environment_config() environment = config.get_default() # Store the environment to zookeeper environment_state_manager = EnvironmentStateManager(self.client) yield environment_state_manager.set_config_state(config, "myfirstenv") # Load the environment with the charm state and charm binary self.provider = environment.get_machine_provider() self.storage = self.provider.get_file_storage() self.charm = CharmDirectory(self.sample_dir1) self.publisher = CharmPublisher(self.client, self.storage) yield self.publisher.add_charm(local_charm_id(self.charm), self.charm) charm_states = yield self.publisher.publish() self.charm_state = charm_states[0] # Create a service from the charm from which we can create units for # the machine. self.service_state_manager = ServiceStateManager(self.client) self.service = yield self.service_state_manager.add_service_state( "fatality-blog", self.charm_state) def process_kill(self, pid): try: os.kill(pid, 9) except OSError: pass @inlineCallbacks def get_agent_config(self): # gets invoked by AgentTestBase.setUp options = yield super(MachineAgentTest, self).get_agent_config() machine_state_manager = MachineStateManager(self.client) self.machine_state = yield machine_state_manager.add_machine_state() self.change_environment( JUJU_MACHINE_ID="0", JUJU_HOME=self.juju_directory) options["machine_id"] = str(self.machine_state.id) # Start the agent with watching enabled returnValue(options) @inlineCallbacks def test_start_begins_watch_and_initializes_directories(self): self.agent.set_watch_enabled(True) mock_machine_state = self.mocker.patch(MachineState) mock_machine_state.watch_assigned_units( self.agent.watch_service_units) self.mocker.replay() yield self.agent.startService() self.assertTrue(os.path.isdir(self.agent.charms_directory)) self.assertTrue(os.path.isdir(self.agent.units_directory)) self.assertTrue(os.path.isdir(self.agent.unit_state_directory)) self.assertIn( "Machine agent started id:%s" % self.agent.get_machine_id(), self.output.getvalue()) yield self.agent.stopService() def test_agent_machine_id_environment_extraction(self): self.change_args("es-agent") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual( config["machine_id"], "0") def test_get_agent_name(self): self.assertEqual(self.agent.get_agent_name(), "Machine:0") def test_agent_machine_id_cli_error(self): """ If the machine id can't be found, a detailed error message is given. """ # initially setup by get_agent_config in setUp self.change_environment(JUJU_MACHINE_ID="") self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address(), "--juju-directory", self.makeDir()) parser = argparse.ArgumentParser() self.agent.setup_options(parser) options = parser.parse_args(namespace=TwistedOptionNamespace()) e = self.assertRaises( JujuError, self.agent.configure, options) self.assertIn( ("--machine-id must be provided in the command line," " or $JUJU_MACHINE_ID in the environment"), str(e)) def test_agent_machine_id_cli_extraction(self): """Command line passing of machine id works and has precedence over environment arg passing.""" self.change_environment(JUJU_MACHINE_ID=str(21)) self.change_args("es-agent", "--machine-id", "0") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual( config["machine_id"], "0") def test_machine_agent_knows_its_machine_id(self): self.assertEqual(self.agent.get_machine_id(), "0") @inlineCallbacks def test_charm_download(self): """ Downloading a charm should store the charm locally. """ yield self.agent.startService() yield self.agent.download_charm(self.charm_state) checksum = self.charm.get_sha256() charm_id = local_charm_id(self.charm) charm_key = under.quote("%s:%s" % (charm_id, checksum)) charm_path = os.path.join(self.agent.charms_directory, charm_key) self.assertTrue(os.path.exists(charm_path)) bundle = CharmBundle(charm_path) self.assertEquals( bundle.get_revision(), self.charm.get_revision()) self.assertEquals(bundle.get_sha256(), checksum) self.assertIn( "Downloading charm %s" % charm_id, self.output.getvalue()) @inlineCallbacks def test_watch_new_service_unit(self): """ Adding a new service unit is detected by the watch. """ self.agent.set_watch_enabled(True) yield self.agent.startService() mock_deployment = self.mocker.patch(self.agent.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) test_deferred = Deferred() def test_complete(machine_id, servers, bundle): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) yield test_deferred self.assertIn( "Units changed old:set([]) new:set(['fatality-blog/0'])", self.output.getvalue()) @inlineCallbacks def test_watch_new_service_unit_error(self): """ An error while starting a new service is logged """ # Inject an error into the service deployment mock_agent = self.mocker.patch(self.agent) mock_agent.start_service_unit("fatality-blog/0") self.mocker.result(fail(SyntaxError("Bad"))) self.mocker.replay() yield self.agent.watch_service_units(None, set(["fatality-blog/0"])) self.assertIn("Starting service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error starting unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("SyntaxError: Bad", self.output.getvalue()) @inlineCallbacks def test_service_unit_removed(self): """ Service unit removed with manual invocation of watch_service_units. """ # Start the agent with watching enabled self.agent.set_watch_enabled(True) yield self.agent.startService() test_deferred = Deferred() mock_deployment = self.mocker.patch(self.agent.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) self.mocker.result(succeed(True)) mock_deployment.destroy() self.mocker.result(succeed(True)) def test_complete(): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) # And now remove it yield self.service_unit.unassign_from_machine() yield test_deferred @inlineCallbacks def test_watch_removed_service_unit_error(self): """ An error while removing a service unit is logged """ mock_agent = self.mocker.patch(self.agent) mock_agent.kill_service_unit("fatality-blog/0") self.mocker.result(fail(OSError("Bad"))) self.mocker.replay() yield self.agent.watch_service_units(set(["fatality-blog/0"]), set()) self.assertIn("Stopping service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error stopping unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("OSError: Bad", self.output.getvalue())
class MachineAgentTest(AgentTestBase, RepositoryTestBase): agent_class = MachineAgent @inlineCallbacks def setUp(self): yield super(MachineAgentTest, self).setUp() self.output = self.capture_logging("juju.agents.machine", level=logging.DEBUG) config = self.get_test_environment_config() environment = config.get_default() # Store the environment to zookeeper environment_state_manager = EnvironmentStateManager(self.client) yield environment_state_manager.set_config_state(config, "myfirstenv") # Load the environment with the charm state and charm binary self.provider = environment.get_machine_provider() self.storage = self.provider.get_file_storage() self.charm = CharmDirectory(self.sample_dir1) self.publisher = CharmPublisher(self.client, self.storage) yield self.publisher.add_charm(local_charm_id(self.charm), self.charm) charm_states = yield self.publisher.publish() self.charm_state = charm_states[0] # Create a service from the charm from which we can create units for # the machine. self.service_state_manager = ServiceStateManager(self.client) self.service = yield self.service_state_manager.add_service_state( "fatality-blog", self.charm_state) def process_kill(self, pid): try: os.kill(pid, 9) except OSError: pass @inlineCallbacks def get_agent_config(self): # gets invoked by AgentTestBase.setUp options = yield super(MachineAgentTest, self).get_agent_config() machine_state_manager = MachineStateManager(self.client) self.machine_state = yield machine_state_manager.add_machine_state() self.change_environment(JUJU_MACHINE_ID="0", JUJU_HOME=self.juju_directory) options["machine_id"] = str(self.machine_state.id) # Start the agent with watching enabled returnValue(options) @inlineCallbacks def test_start_begins_watch_and_initializes_directories(self): self.agent.set_watch_enabled(True) mock_machine_state = self.mocker.patch(MachineState) mock_machine_state.watch_assigned_units(self.agent.watch_service_units) self.mocker.replay() yield self.agent.startService() self.assertTrue(os.path.isdir(self.agent.charms_directory)) self.assertTrue(os.path.isdir(self.agent.units_directory)) self.assertTrue(os.path.isdir(self.agent.unit_state_directory)) self.assertIn( "Machine agent started id:%s" % self.agent.get_machine_id(), self.output.getvalue()) yield self.agent.stopService() def test_agent_machine_id_environment_extraction(self): self.change_args("es-agent") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual(config["machine_id"], "0") def test_get_agent_name(self): self.assertEqual(self.agent.get_agent_name(), "Machine:0") def test_agent_machine_id_cli_error(self): """ If the machine id can't be found, a detailed error message is given. """ # initially setup by get_agent_config in setUp self.change_environment(JUJU_MACHINE_ID="") self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address(), "--juju-directory", self.makeDir()) parser = argparse.ArgumentParser() self.agent.setup_options(parser) options = parser.parse_args(namespace=TwistedOptionNamespace()) e = self.assertRaises(JujuError, self.agent.configure, options) self.assertIn(("--machine-id must be provided in the command line," " or $JUJU_MACHINE_ID in the environment"), str(e)) def test_agent_machine_id_cli_extraction(self): """Command line passing of machine id works and has precedence over environment arg passing.""" self.change_environment(JUJU_MACHINE_ID=str(21)) self.change_args("es-agent", "--machine-id", "0") parser = argparse.ArgumentParser() self.agent.setup_options(parser) config = parser.parse_args(namespace=TwistedOptionNamespace()) self.assertEqual(config["machine_id"], "0") def test_machine_agent_knows_its_machine_id(self): self.assertEqual(self.agent.get_machine_id(), "0") @inlineCallbacks def test_charm_download(self): """ Downloading a charm should store the charm locally. """ yield self.agent.startService() yield self.agent.download_charm(self.charm_state) checksum = self.charm.get_sha256() charm_id = local_charm_id(self.charm) charm_key = under.quote("%s:%s" % (charm_id, checksum)) charm_path = os.path.join(self.agent.charms_directory, charm_key) self.assertTrue(os.path.exists(charm_path)) bundle = CharmBundle(charm_path) self.assertEquals(bundle.get_revision(), self.charm.get_revision()) self.assertEquals(bundle.get_sha256(), checksum) self.assertIn("Downloading charm %s" % charm_id, self.output.getvalue()) @inlineCallbacks def test_watch_new_service_unit(self): """ Adding a new service unit is detected by the watch. """ self.agent.set_watch_enabled(True) yield self.agent.startService() mock_deployment = self.mocker.patch(self.agent.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) test_deferred = Deferred() def test_complete(machine_id, servers, bundle): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) yield test_deferred self.assertIn("Units changed old:set([]) new:set(['fatality-blog/0'])", self.output.getvalue()) @inlineCallbacks def test_watch_new_service_unit_error(self): """ An error while starting a new service is logged """ # Inject an error into the service deployment mock_agent = self.mocker.patch(self.agent) mock_agent.start_service_unit("fatality-blog/0") self.mocker.result(fail(SyntaxError("Bad"))) self.mocker.replay() yield self.agent.watch_service_units(None, set(["fatality-blog/0"])) self.assertIn("Starting service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error starting unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("SyntaxError: Bad", self.output.getvalue()) @inlineCallbacks def test_service_unit_removed(self): """ Service unit removed with manual invocation of watch_service_units. """ # Start the agent with watching enabled self.agent.set_watch_enabled(True) yield self.agent.startService() test_deferred = Deferred() mock_deployment = self.mocker.patch(self.agent.deploy_factory) mock_deployment.start("0", get_test_zookeeper_address(), MATCH_BUNDLE) self.mocker.result(succeed(True)) mock_deployment.destroy() self.mocker.result(succeed(True)) def test_complete(): test_deferred.callback(True) self.mocker.call(test_complete) self.mocker.replay() # Create a new service unit self.service_unit = yield self.service.add_unit_state() yield self.service_unit.assign_to_machine(self.machine_state) # And now remove it yield self.service_unit.unassign_from_machine() yield test_deferred @inlineCallbacks def test_watch_removed_service_unit_error(self): """ An error while removing a service unit is logged """ mock_agent = self.mocker.patch(self.agent) mock_agent.kill_service_unit("fatality-blog/0") self.mocker.result(fail(OSError("Bad"))) self.mocker.replay() yield self.agent.watch_service_units(set(["fatality-blog/0"]), set()) self.assertIn("Stopping service unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("Error stopping unit: %s" % "fatality-blog/0", self.output.getvalue()) self.assertIn("OSError: Bad", self.output.getvalue())