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 start(self): """Start the machine agent. Creates state directories on the machine, retrieves the machine state, and enables watch on assigned units. """ if not os.path.exists(self.units_directory): os.makedirs(self.units_directory) if not os.path.exists(self.unit_state_directory): os.makedirs(self.unit_state_directory) # Get state managers we'll be utilizing. self.service_state_manager = ServiceStateManager(self.client) self.unit_deployer = UnitDeployer( self.client, self.get_machine_id(), self.config["juju_directory"]) yield self.unit_deployer.start() # Retrieve the machine state for the machine we represent. machine_manager = MachineStateManager(self.client) self.machine_state = yield machine_manager.get_machine_state( self.get_machine_id()) # Watch assigned units for the machine. if self.get_watch_enabled(): self.machine_state.watch_assigned_units( self.watch_service_units) # Connect the machine agent, broadcasting presence to the world. yield self.machine_state.connect_agent() log.info("Machine agent started id:%s" % self.get_machine_id())
def _do_unit_deploy(self, unit_name, machine_id, charm_dir): # this method exists to aid testing rather than being an # inline unit_deployer = UnitDeployer(self._client, machine_id, charm_dir) yield unit_deployer.start("subordinate") yield unit_deployer.start_service_unit(unit_name)
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"])
class MachineAgent(BaseAgent): """A juju machine agent. The machine agent is responsible for monitoring service units assigned to a machine. If a new unit is assigned to machine, the machine agent will download the charm, create a working space for the service unit agent, and then launch it. Additionally the machine agent will monitor the running service unit agents on the machine, via their ephemeral nodes, and restart them if they die. """ name = "juju-machine-agent" unit_agent_module = "juju.agents.unit" @property def units_directory(self): return os.path.join(self.config["juju_directory"], "units") @property def unit_state_directory(self): return os.path.join(self.config["juju_directory"], "state") @inlineCallbacks def start(self): """Start the machine agent. Creates state directories on the machine, retrieves the machine state, and enables watch on assigned units. """ if not os.path.exists(self.units_directory): os.makedirs(self.units_directory) if not os.path.exists(self.unit_state_directory): os.makedirs(self.unit_state_directory) # Get state managers we'll be utilizing. self.service_state_manager = ServiceStateManager(self.client) self.unit_deployer = UnitDeployer( self.client, self.get_machine_id(), self.config["juju_directory"]) yield self.unit_deployer.start() # Retrieve the machine state for the machine we represent. machine_manager = MachineStateManager(self.client) self.machine_state = yield machine_manager.get_machine_state( self.get_machine_id()) # Watch assigned units for the machine. if self.get_watch_enabled(): self.machine_state.watch_assigned_units( self.watch_service_units) # Connect the machine agent, broadcasting presence to the world. yield self.machine_state.connect_agent() log.info("Machine agent started id:%s" % self.get_machine_id()) @inlineCallbacks def watch_service_units(self, old_units, new_units): """Callback invoked when the assigned service units change. """ if old_units is None: old_units = set() log.debug( "Units changed old:%s new:%s", old_units, new_units) stopped = old_units - new_units started = new_units - old_units for unit_name in stopped: log.debug("Stopping service unit: %s ...", unit_name) try: yield self.unit_deployer.kill_service_unit(unit_name) except Exception: log.exception("Error stopping unit: %s", unit_name) for unit_name in started: log.debug("Starting service unit: %s ...", unit_name) try: yield self.unit_deployer.start_service_unit(unit_name) except Exception: log.exception("Error starting unit: %s", unit_name) def get_machine_id(self): """Get the id of the machine as known within the zk state.""" return self.config["machine_id"] def get_agent_name(self): return "Machine:%s" % (self.get_machine_id()) def configure(self, options): super(MachineAgent, self).configure(options) if not options.get("machine_id"): msg = ("--machine-id must be provided in the command line, " "or $JUJU_MACHINE_ID in the environment") raise JujuError(msg) @classmethod def setup_options(cls, parser): super(MachineAgent, cls).setup_options(parser) machine_id = os.environ.get("JUJU_MACHINE_ID", "") parser.add_argument( "--machine-id", default=machine_id) return parser