Ejemplo n.º 1
0
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"])
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
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