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:
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)
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 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:
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)