def test_incompatible_endpoints(self): error = IncompatibleEndpoints( RelationEndpoint("mysql", "mysql", "db", "server"), RelationEndpoint("riak", "riak", "ring", "peer")) self.assertIsStateError(error) self.assertTrue("mysql" in str(error)) self.assertTrue("riak" in str(error))
def test_relation_already_exists(self): error = RelationAlreadyExists( RelationEndpoint("wordpress", "mysql", "mysql", "client"), RelationEndpoint("mysql", "mysql", "db", "server")) self.assertIsStateError(error) self.assertTrue("wordpress" in str(error)) self.assertTrue("mysql" in str(error))
def _default_relations(self): wordpress_ep = RelationEndpoint("wordpress", "client-server", "app", "client") mysql_ep = RelationEndpoint("mysql", "client-server", "db", "server") self.wordpress_states = yield self.\ add_relation_service_unit_from_endpoints(wordpress_ep, mysql_ep) self.mysql_states = yield self.add_opposite_service_unit( self.wordpress_states) self.relation = self.mysql_states["unit_relation"]
def test_has_relation_between_dyadic_endpoints_missing_assignment(self): mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") self.topology.add_service("s-0", "wordpress") self.topology.add_service("s-1", "mysqldb") self.topology.add_relation("r-0", "mysql") self.topology.assign_service_to_relation("r-0", "s-1", "db", "server") self.assertFalse( self.topology.has_relation_between_endpoints([mysql_ep, blog_ep])) self.assertFalse( self.topology.has_relation_between_endpoints([blog_ep, mysql_ep]))
def setUp(self): yield super(ExecutionContextTest, self).setUp() wordpress_ep = RelationEndpoint("wordpress", "client-server", "", "client") mysql_ep = RelationEndpoint("mysql", "client-server", "", "server") self.wordpress_states = yield self.\ add_relation_service_unit_from_endpoints( wordpress_ep, mysql_ep) self.mysql_states = yield self.add_opposite_service_unit( self.wordpress_states) self.relation = self.mysql_states["relation"]
def setup_default_test_relation(self): mysql_ep = RelationEndpoint("mysql", "client-server", "app", "server") wordpress_ep = RelationEndpoint("wordpress", "client-server", "db", "client") self.states = yield self.add_relation_service_unit_from_endpoints( mysql_ep, wordpress_ep) self.unit_directory = os.path.join( self.juju_directory, "units", self.states["unit"].unit_name.replace("/", "-")) os.makedirs(os.path.join(self.unit_directory, "charm", "hooks")) os.makedirs(os.path.join(self.juju_directory, "state"))
def test_get_relation_between_dyadic_endpoints(self): mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") self.topology.add_service("s-0", "wordpress") self.topology.add_service("s-1", "mysqldb") self.topology.add_relation("r-0", "mysql") self.topology.assign_service_to_relation("r-0", "s-0", "mysql", "client") self.topology.assign_service_to_relation("r-0", "s-1", "db", "server") self.assertEqual( self.topology.get_relation_between_endpoints([mysql_ep, blog_ep]), "r-0") self.assertEqual( self.topology.get_relation_between_endpoints([blog_ep, mysql_ep]), "r-0")
def test_has_relation_between_monadic_endpoints(self): riak_ep = RelationEndpoint("riak", "riak", "riak", "peer") self.topology.add_service("s-0", "riak") self.topology.add_relation("r-0", "riak") self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer") self.assertTrue(self.topology.has_relation_between_endpoints([riak_ep ]))
def test_service_relation_watching(self): """When the unit lifecycle is started, the assigned relations of the service are watched, with unit relation lifecycles created for each. Relation hook invocation do not maintain global order or determinism across relations. They only maintain ordering and determinism within a relation. A shared scheduler across relations would be needed to maintain such behavior. """ file_path = self.write_start_and_relation_hooks() wordpress1_states = yield self.add_opposite_service_unit(self.states) yield self.lifecycle.start() yield self.wait_on_hook("app-relation-changed") self.assertTrue(os.path.exists(file_path)) self.assertEqual([x.strip() for x in open(file_path).readlines()], ["joined", "changed"]) # Queue up our wait condition, of 4 hooks firing hooks_complete = self.wait_on_hook(sequence=[ "app-relation-joined", # joined event fires join hook, "app-relation-changed", # followed by changed hook "app-relation-changed", "app-relation-departed" ]) # add another. wordpress2_states = yield self.add_opposite_service_unit( (yield self.add_relation_service_unit_to_another_endpoint( self.states, RelationEndpoint("wordpress-2", "client-server", "db", "client")))) # modify one. wordpress1_states["unit_relation"].set_data({"hello": "world"}) # delete one. self.client.delete("/relations/%s/client/%s" % (wordpress2_states["relation"].internal_id, wordpress2_states["unit"].internal_id)) # verify results, waiting for hooks to complete yield hooks_complete self.assertEqual( set([x.strip() for x in open(file_path).readlines()]), set(["joined", "changed", "joined", "changed", "departed"]))
def endpoints(*pairs): return [(RelationEndpoint(*pair[0].split()), RelationEndpoint(*pair[1].split())) for pair in pairs]
def test_duplicate_endpoints(self): riak_ep = RelationEndpoint("riak", "riak", "ring", "peer") error = DuplicateEndpoints(riak_ep, riak_ep) self.assertIsStateError(error) self.assertTrue("riak" in str(error))
def test_may_relate_to(self): # TODO: Needs a doc string mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") blog_ep = RelationEndpoint("blog", "mysql", "mysql", "client") pg_ep = RelationEndpoint("postgres", "postgres", "db", "server") self.assertRaises(TypeError, mysql_ep.may_relate_to, 42) # should relate, along with symmetric case self.assert_(mysql_ep.may_relate_to(blog_ep)) self.assert_(blog_ep.may_relate_to(mysql_ep)) # no common relation_type self.assertFalse(blog_ep.may_relate_to(pg_ep)) self.assertFalse(pg_ep.may_relate_to(blog_ep)) # antireflexive om relation_role - # must be consumer AND provider or vice versa self.assertFalse(blog_ep.may_relate_to( RelationEndpoint("foo", "mysql", "db", "client"))) self.assertFalse(mysql_ep.may_relate_to( RelationEndpoint("foo", "mysql", "db", "server"))) # irreflexive for server/client self.assertFalse(mysql_ep.may_relate_to(mysql_ep)) self.assertFalse(blog_ep.may_relate_to(blog_ep)) self.assertFalse(pg_ep.may_relate_to(pg_ep)) # but reflexive for peer riak_ep = RelationEndpoint("riak", "riak", "riak", "peer") self.assert_(riak_ep.may_relate_to(riak_ep))
def test_may_relate_to(self): # TODO: Needs a doc string mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") blog_ep = RelationEndpoint("blog", "mysql", "mysql", "client") pg_ep = RelationEndpoint("postgres", "postgres", "db", "server") self.assertRaises(TypeError, mysql_ep.may_relate_to, 42) # should relate, along with symmetric case self.assert_(mysql_ep.may_relate_to(blog_ep)) self.assert_(blog_ep.may_relate_to(mysql_ep)) # no common relation_type self.assertFalse(blog_ep.may_relate_to(pg_ep)) self.assertFalse(pg_ep.may_relate_to(blog_ep)) # antireflexive om relation_role - # must be consumer AND provider or vice versa self.assertFalse( blog_ep.may_relate_to( RelationEndpoint("foo", "mysql", "db", "client"))) self.assertFalse( mysql_ep.may_relate_to( RelationEndpoint("foo", "mysql", "db", "server"))) # irreflexive for server/client self.assertFalse(mysql_ep.may_relate_to(mysql_ep)) self.assertFalse(blog_ep.may_relate_to(blog_ep)) self.assertFalse(pg_ep.may_relate_to(pg_ep)) # but reflexive for peer riak_ep = RelationEndpoint("riak", "riak", "riak", "peer") self.assert_(riak_ep.may_relate_to(riak_ep))
def test_peer_relation(self): """Verify status works with peer relations. """ m1 = yield self.machine_state_manager.add_machine_state() m2 = yield self.machine_state_manager.add_machine_state() yield self.provider.start_machine({ "machine-id": 0, "dns-name": "steamcloud-1.com" }) yield self.provider.start_machine({ "machine-id": 1, "dns-name": "steamcloud-2.com" }) yield m1.set_instance_id(0) yield m2.set_instance_id(1) riak = yield self.add_service_from_charm("riak") riak_u1 = yield self.add_unit(riak, m1) riak_u2 = yield self.add_unit(riak, m2, with_agent=lambda _: False) yield self.set_unit_state(riak_u1, "started") yield self.set_unit_state(riak_u2, "started") _, (peer_rel, ) = yield self.relation_state_manager.add_relation_state( RelationEndpoint("riak", "peer", "ring", "peer")) yield ZookeeperWorkflowState( self.client, (yield peer_rel.add_unit_state(riak_u1))).set_state("up") yield peer_rel.add_unit_state(riak_u2) state = yield status.collect(["riak"], self.provider, self.client, None) self.assertEqual( state["services"]["riak"], { "charm": "local:series/riak-7", "relations": { "ring": "riak" }, "units": { "riak/0": { "machine": 0, "public-address": "riak-0.example.com", "relations": { "ring": { "state": "up" } }, "state": "started" }, "riak/1": { "machine": 1, "public-address": "riak-1.example.com", "relations": { "ring": { "state": None } }, "state": "down" } } })
def build_topology(self, base=None, skip_unit_agents=()): """Build a simulated topology with a default machine configuration. This method returns a dict that can be used to get handles to the constructed objects. """ state = {} # build out the topology using the state managers m1 = yield self.machine_state_manager.add_machine_state() m2 = yield self.machine_state_manager.add_machine_state() m3 = yield self.machine_state_manager.add_machine_state() m4 = yield self.machine_state_manager.add_machine_state() m5 = yield self.machine_state_manager.add_machine_state() m6 = yield self.machine_state_manager.add_machine_state() m7 = yield self.machine_state_manager.add_machine_state() # inform the provider about the machine yield self.provider.start_machine({ "machine-id": 0, "dns-name": "steamcloud-1.com" }) yield self.provider.start_machine({ "machine-id": 1, "dns-name": "steamcloud-2.com" }) yield self.provider.start_machine({ "machine-id": 2, "dns-name": "steamcloud-3.com" }) yield self.provider.start_machine({ "machine-id": 3, "dns-name": "steamcloud-4.com" }) yield self.provider.start_machine({ "machine-id": 4, "dns-name": "steamcloud-5.com" }) yield self.provider.start_machine({ "machine-id": 5, "dns-name": "steamcloud-6.com" }) yield self.provider.start_machine({ "machine-id": 6, "dns-name": "steamcloud-7.com" }) yield m1.set_instance_id(0) yield m2.set_instance_id(1) yield m3.set_instance_id(2) yield m4.set_instance_id(3) yield m5.set_instance_id(4) yield m6.set_instance_id(5) yield m7.set_instance_id(6) state["machines"] = [m1, m2, m3, m4, m5, m6, m7] # "Deploy" services wordpress = yield self.add_service_from_charm("wordpress") mysql = yield self.add_service_from_charm("mysql") yield mysql.set_exposed_flag() # but w/ no open ports varnish = yield self.add_service_from_charm("varnish") yield varnish.set_exposed_flag() # w/o additional metadata memcache = yield self.add_service("memcache") state["services"] = dict(wordpress=wordpress, mysql=mysql, varnish=varnish, memcache=memcache) def with_unit(name): for pattern in skip_unit_agents: if fnmatch(name, pattern): return False return True wpu = yield self.add_unit(wordpress, m1, with_unit) myu1 = yield self.add_unit(mysql, m2, with_unit) myu2 = yield self.add_unit(mysql, m3, with_unit) vu1 = yield self.add_unit(varnish, m4, with_unit) vu2 = yield self.add_unit(varnish, m5, with_unit) mc1 = yield self.add_unit(memcache, m6, with_unit) mc2 = yield self.add_unit(memcache, m7, with_unit) # add unit states to services and assign to machines # Set the lifecycle state and open ports, if any, for each unit state. yield self.set_unit_state(wpu, "started", [(80, "tcp"), (443, "tcp")]) yield self.set_unit_state(myu1, "started") yield self.set_unit_state(myu2, "stopped") yield self.set_unit_state(vu1, "started", [(80, "tcp")]) yield self.set_unit_state(vu2, "started", [(80, "tcp")]) yield self.set_unit_state(mc1, None) yield self.set_unit_state(mc2, "installed") # Wordpress integrates with each of the following # services. Each relation endpoint is used to define the # specific relation to be established. mysql_ep = RelationEndpoint("mysql", "client-server", "db", "server") memcache_ep = RelationEndpoint("memcache", "client-server", "cache", "server") varnish_ep = RelationEndpoint("varnish", "client-server", "proxy", "client") wordpress_db_ep = RelationEndpoint("wordpress", "client-server", "db", "client") wordpress_cache_ep = RelationEndpoint("wordpress", "client-server", "cache", "client") wordpress_proxy_ep = RelationEndpoint("wordpress", "client-server", "proxy", "server") # Create relation service units for each of these relations yield self.add_relation_with_relation_units(mysql_ep, [myu1, myu2], ["up", "departed"], wordpress_db_ep, [wpu], ["up"]) yield self.add_relation_with_relation_units(memcache_ep, [mc1, mc2], ["up", "down"], wordpress_cache_ep, [wpu], ["up"]) yield self.add_relation_with_relation_units(varnish_ep, [vu1, vu2], ["up", "up"], wordpress_proxy_ep, [wpu], ["up"]) state["relations"] = dict(wordpress=[wpu], mysql=[myu1, myu2], varnish=[vu1, vu2], memcache=[mc1, mc2]) returnValue(state)
def test_lifecycle_start_stop_starts_relations(self): """Starting a stopped lifecycle, restarts relation events. """ wordpress1_states = yield self.add_opposite_service_unit(self.states) wordpress2_states = yield self.add_opposite_service_unit( (yield self.add_relation_service_unit_to_another_endpoint( self.states, RelationEndpoint("wordpress-2", "client-server", "db", "client")))) # Start and stop lifecycle file_path = self.write_start_and_relation_hooks() yield self.lifecycle.start() yield self.wait_on_hook("app-relation-changed") self.assertTrue(os.path.exists(file_path)) yield self.lifecycle.stop() ######################################################## # Add, remove relations, and modify related unit settings. # The following isn't enough to trigger a hook notification. # yield wordpress1_states["relation"].unassign_service( # wordpress1_states["service"]) # # The removal of the external relation, means we stop getting notifies # of it, but the underlying unit agents of the service are responsible # for removing their presence nodes within the relationship, which # triggers a hook invocation. yield self.client.delete("/relations/%s/client/%s" % (wordpress1_states["relation"].internal_id, wordpress1_states["unit"].internal_id)) yield wordpress2_states["unit_relation"].set_data({"hello": "world"}) yield self.add_opposite_service_unit( (yield self.add_relation_service_unit_to_another_endpoint( self.states, RelationEndpoint("wordpress-3", "client-server", "db", "client")))) # Verify no hooks are executed. yield self.sleep(0.1) res = [x.strip() for x in open(file_path)] if ((res != ["joined", "changed", "joined", "changed"]) and (res != ["joined", "joined", "changed", "changed"])): self.fail("Invalid join sequence %s" % res) # XXX - With scheduler local state recovery, we should get the modify. # Start and verify events. hooks_executed = self.wait_on_hook(sequence=[ "config-changed", "start", "app-relation-departed", "app-relation-joined", # joined event fires joined hook, "app-relation-changed" # followed by changed hook ]) yield self.lifecycle.start() yield hooks_executed res.extend(["departed", "joined", "changed"]) self.assertEqual([x.strip() for x in open(file_path)], res)