Exemple #1
0
class CharmStateManagerTest(StateTestBase):
    @inlineCallbacks
    def setUp(self):
        yield super(CharmStateManagerTest, self).setUp()
        self.charm_state_manager = CharmStateManager(self.client)
        self.charm_id = local_charm_id(self.charm)

    @inlineCallbacks
    def test_add_charm(self):
        """
        Adding a Charm into a CharmStateManager should register
        the charm within the Zookeeper state, according to the
        specification.
        """
        charm_state = yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "http://example.com/abc")
        self.assertEquals(charm_state.id, "local:series/dummy-1")

        children = yield self.client.get_children("/charms")
        self.assertEquals(children, ["local_3a_series_2f_dummy-1"])

        content, stat = yield self.client.get(
            "/charms/local_3a_series_2f_dummy-1")

        charm_data = yaml.load(content)
        self.assertEquals(
            charm_data, {
                "metadata": self.charm.metadata.get_serialization_data(),
                "config": self.charm.config.get_serialization_data(),
                "sha256": self.charm.get_sha256(),
                "url": "http://example.com/abc"
            })

    @inlineCallbacks
    def test_get_charm(self):
        """
        A CharmState should be available if one get()s a charm
        that was previously added into the manager.
        """
        yield self.charm_state_manager.add_charm_state(self.charm_id,
                                                       self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        self.assertEquals(charm_state.id, "local:series/dummy-1")

    @inlineCallbacks
    def test_charm_state_attributes(self):
        """
        Verify that the basic (invariant) attributes of the
        CharmState are correctly in place.
        """
        yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "http://example.com/abc")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        self.assertEquals(charm_state.name, "dummy")
        self.assertEquals(charm_state.revision, 1)
        self.assertEquals(charm_state.id, "local:series/dummy-1")
        self.assertEquals(charm_state.bundle_url, "http://example.com/abc")

    @inlineCallbacks
    def test_charm_state_metadata(self):
        """
        Check that the charm metadata was correctly saved and loaded.
        """
        yield self.charm_state_manager.add_charm_state(self.charm_id,
                                                       self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        metadata = yield charm_state.get_metadata()
        self.assertEquals(metadata.name, "dummy")

    @inlineCallbacks
    def test_charm_state_config_options(self):
        """Verify ConfigOptions present and correct."""
        from juju.charm.tests.test_config import sample_yaml_data

        yield self.charm_state_manager.add_charm_state(self.charm_id,
                                                       self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        config = yield charm_state.get_config()
        self.assertEquals(config.get_serialization_data(), sample_yaml_data)

    @inlineCallbacks
    def test_get_non_existing_charm_prior_to_initialization(self):
        """
        Getting a charm before the charms node was even
        initialized should raise an error about the charm not
        being present.
        """
        try:
            yield self.charm_state_manager.get_charm_state(
                "local:series/dummy-1")
        except CharmStateNotFound, e:
            self.assertEquals(e.charm_id, "local:series/dummy-1")
        else:
Exemple #2
0
class CharmPublisher(object):
    """
    Publishes a charm to an environment.
    """

    def __init__(self, client, storage):
        self._client = client
        self._storage = storage
        self._charm_state_manager = CharmStateManager(self._client)
        self._charm_add_queue = []
        self._charm_state_cache = {}

    @classmethod
    @inlineCallbacks
    def for_environment(cls, environment):
        provider = environment.get_machine_provider()
        storage = provider.get_file_storage()
        client = yield provider.connect()
        returnValue(cls(client, storage))

    @inlineCallbacks
    def add_charm(self, charm_id, charm):
        """Schedule a charm for addition to an juju environment.

        Returns true if the charm is scheduled for upload, false if
        the charm is already present in juju.
        """
        self._charm_add_queue.append((charm_id, charm))
        if charm_id in self._charm_state_cache:
            returnValue(False)
        try:
            state = yield self._charm_state_manager.get_charm_state(
                charm_id)
        except CharmStateNotFound:
            pass
        else:
            log.info("Using cached charm version of %s" % charm.metadata.name)
            self._charm_state_cache[charm_id] = state
            returnValue(False)
        returnValue(True)

    def _publish_one(self, charm_id, charm):
        if charm_id in self._charm_state_cache:
            return succeed(self._charm_state_cache[charm_id])

        bundle = charm.as_bundle()
        charm_file = open(bundle.path, "rb")
        charm_store_path = under.quote(
            "%s:%s" % (charm_id, bundle.get_sha256()))

        def close_charm_file(passthrough):
            charm_file.close()
            return passthrough

        def get_charm_url(result):
            return self._storage.get_url(charm_store_path)

        d = self._storage.put(charm_store_path, charm_file)
        d.addBoth(close_charm_file)
        d.addCallback(get_charm_url)
        d.addCallback(self._cb_store_charm_state, charm_id, bundle)
        d.addErrback(self._eb_verify_duplicate, charm_id, bundle)
        return d

    def publish(self):
        """Publish all added charms to provider storage and zookeeper.

        Returns the charm_state of all scheduled charms.
        """
        publish_deferreds = []
        for charm_id, charm in self._charm_add_queue:
            publish_deferreds.append(self._publish_one(charm_id, charm))

        publish_deferred = DeferredList(publish_deferreds,
                                        fireOnOneErrback=1,
                                        consumeErrors=1)
        # callbacks and deferreds to unwind the dlist
        publish_deferred.addCallback(self._cb_extract_charm_state)
        publish_deferred.addErrback(self._eb_extract_error)
        return publish_deferred

    def _cb_extract_charm_state(self, result):
        return [r[1] for r in result]

    def _eb_extract_error(self, failure):
        failure.trap(FirstError)
        return failure.value.subFailure

    def _cb_store_charm_state(self, charm_url, charm_id, charm):
        return self._charm_state_manager.add_charm_state(
            charm_id, charm, charm_url)

    @inlineCallbacks
    def _eb_verify_duplicate(self, failure, charm_id, charm):
        """Detects duplicates vs. conflicts, raises stateerror on conflict."""
        failure.trap(NodeExistsException)

        try:
            charm_state = \
                yield self._charm_state_manager.get_charm_state(charm_id)
        except NoNodeException:
            # Check if the state goes away due to concurrent removal
            msg = "Charm removed concurrently during publish, please retry."
            raise StateChanged(msg)

        if charm_state.get_sha256() != charm.get_sha256():
            msg = "Concurrent upload of charm has different checksum %s" % (
                charm_id)
            raise StateChanged(msg)
Exemple #3
0
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:
Exemple #4
0
class CharmStateManagerTest(StateTestBase):

    @inlineCallbacks
    def setUp(self):
        yield super(CharmStateManagerTest, self).setUp()
        self.charm_state_manager = CharmStateManager(self.client)
        self.charm_id = local_charm_id(self.charm)

    @inlineCallbacks
    def test_add_charm(self):
        """
        Adding a Charm into a CharmStateManager should register
        the charm within the Zookeeper state, according to the
        specification.
        """
        charm_state = yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "http://example.com/abc")
        self.assertEquals(charm_state.id, "local:series/dummy-1")

        children = yield self.client.get_children("/charms")
        self.assertEquals(children, ["local_3a_series_2f_dummy-1"])

        content, stat = yield self.client.get(
            "/charms/local_3a_series_2f_dummy-1")

        charm_data = yaml.load(content)
        self.assertEquals(charm_data, {
            "metadata": self.charm.metadata.get_serialization_data(),
            "config": self.charm.config.get_serialization_data(),
            "sha256": self.charm.get_sha256(),
            "url": "http://example.com/abc"
        })

    @inlineCallbacks
    def test_get_charm(self):
        """
        A CharmState should be available if one get()s a charm
        that was previously added into the manager.
        """
        yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        self.assertEquals(charm_state.id, "local:series/dummy-1")

    @inlineCallbacks
    def test_charm_state_attributes(self):
        """
        Verify that the basic (invariant) attributes of the
        CharmState are correctly in place.
        """
        yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "http://example.com/abc")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        self.assertEquals(charm_state.name, "dummy")
        self.assertEquals(charm_state.revision, 1)
        self.assertEquals(charm_state.id, "local:series/dummy-1")
        self.assertEquals(charm_state.bundle_url, "http://example.com/abc")

    @inlineCallbacks
    def test_charm_state_metadata(self):
        """
        Check that the charm metadata was correctly saved and loaded.
        """
        yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        metadata = yield charm_state.get_metadata()
        self.assertEquals(metadata.name, "dummy")

    @inlineCallbacks
    def test_charm_state_config_options(self):
        """Verify ConfigOptions present and correct."""
        from juju.charm.tests.test_config import sample_yaml_data

        yield self.charm_state_manager.add_charm_state(
            self.charm_id, self.charm, "")
        charm_state = yield self.charm_state_manager.get_charm_state(
            "local:series/dummy-1")
        config = yield charm_state.get_config()
        self.assertEquals(config.get_serialization_data(),
                          sample_yaml_data)

    @inlineCallbacks
    def test_get_non_existing_charm_prior_to_initialization(self):
        """
        Getting a charm before the charms node was even
        initialized should raise an error about the charm not
        being present.
        """
        try:
            yield self.charm_state_manager.get_charm_state(
                "local:series/dummy-1")
        except CharmStateNotFound, e:
            self.assertEquals(e.charm_id, "local:series/dummy-1")
        else:
Exemple #5
0
class CharmPublisher(object):
    """
    Publishes a charm to an environment.
    """
    def __init__(self, client, storage):
        self._client = client
        self._storage = storage
        self._charm_state_manager = CharmStateManager(self._client)
        self._charm_add_queue = []
        self._charm_state_cache = {}

    @classmethod
    @inlineCallbacks
    def for_environment(cls, environment):
        provider = environment.get_machine_provider()
        storage = provider.get_file_storage()
        client = yield provider.connect()
        returnValue(cls(client, storage))

    @inlineCallbacks
    def add_charm(self, charm_id, charm):
        """Schedule a charm for addition to an juju environment.

        Returns true if the charm is scheduled for upload, false if
        the charm is already present in juju.
        """
        self._charm_add_queue.append((charm_id, charm))
        if charm_id in self._charm_state_cache:
            returnValue(False)
        try:
            state = yield self._charm_state_manager.get_charm_state(charm_id)
        except CharmStateNotFound:
            pass
        else:
            self._charm_state_cache[charm_id] = state
            returnValue(False)
        returnValue(True)

    def _publish_one(self, charm_id, charm):
        if charm_id in self._charm_state_cache:
            return succeed(self._charm_state_cache[charm_id])

        bundle = charm.as_bundle()
        charm_file = open(bundle.path, "rb")
        charm_store_path = under.quote("%s:%s" %
                                       (charm_id, bundle.get_sha256()))

        def close_charm_file(passthrough):
            charm_file.close()
            return passthrough

        def get_charm_url(result):
            return self._storage.get_url(charm_store_path)

        d = self._storage.put(charm_store_path, charm_file)
        d.addBoth(close_charm_file)
        d.addCallback(get_charm_url)
        d.addCallback(self._cb_store_charm_state, charm_id, bundle)
        d.addErrback(self._eb_verify_duplicate, charm_id, bundle)
        return d

    def publish(self):
        """Publish all added charms to provider storage and zookeeper.

        Returns the charm_state of all scheduled charms.
        """
        publish_deferreds = []
        for charm_id, charm in self._charm_add_queue:
            publish_deferreds.append(self._publish_one(charm_id, charm))

        publish_deferred = DeferredList(publish_deferreds,
                                        fireOnOneErrback=1,
                                        consumeErrors=1)
        # callbacks and deferreds to unwind the dlist
        publish_deferred.addCallback(self._cb_extract_charm_state)
        publish_deferred.addErrback(self._eb_extract_error)
        return publish_deferred

    def _cb_extract_charm_state(self, result):
        return [r[1] for r in result]

    def _eb_extract_error(self, failure):
        failure.trap(FirstError)
        return failure.value.subFailure

    def _cb_store_charm_state(self, charm_url, charm_id, charm):
        return self._charm_state_manager.add_charm_state(
            charm_id, charm, charm_url)

    @inlineCallbacks
    def _eb_verify_duplicate(self, failure, charm_id, charm):
        """Detects duplicates vs. conflicts, raises stateerror on conflict."""
        failure.trap(NodeExistsException)

        try:
            charm_state = \
                yield self._charm_state_manager.get_charm_state(charm_id)
        except NoNodeException:
            # Check if the state goes away due to concurrent removal
            msg = "Charm removed concurrently during publish, please retry."
            raise StateChanged(msg)

        if charm_state.get_sha256() != charm.get_sha256():
            msg = "Concurrent upload of charm has different checksum %s" % (
                charm_id)
            raise StateChanged(msg)