def terminate_machine(config, environment, verbose, log, machine_ids): """Terminates the machines in `machine_ids`. Like the underlying code in MachineStateManager, it's permissible if the machine ID is already terminated or even never running. If we determine this is not desired behavior, presumably propagate that back to the state manager. XXX However, we currently special case support of not terminating the "root" machine, that is the one running the provisioning agent. At some point, this will be managed like any other service, but until then it seems best to ensure it's not terminated at this level. """ provider = environment.get_machine_provider() client = yield provider.connect() terminated_machine_ids = [] try: machine_state_manager = MachineStateManager(client) for machine_id in machine_ids: if machine_id == 0: raise CannotTerminateMachine(0, "environment would be destroyed") removed = yield machine_state_manager.remove_machine_state( machine_id) if not removed: raise MachineStateNotFound(machine_id) terminated_machine_ids.append(machine_id) finally: yield client.close() if terminated_machine_ids: log.info("Machines terminated: %s", ", ".join(str(id) for id in terminated_machine_ids))
def test_open_close_ports_on_machine(self): """Verify opening/closing ports on a machine works properly. In particular this is done without watch support.""" manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() yield self.firewall_manager.process_machine(machine) # Expose a service wordpress = yield self.add_service("wordpress") yield wordpress.set_exposed_flag() wordpress_0 = yield wordpress.add_unit_state() yield wordpress_0.open_port(80, "tcp") yield wordpress_0.open_port(443, "tcp") yield wordpress_0.assign_to_machine(machine) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set([(80, "tcp"), (443, "tcp")])) self.assertIn("Opened 80/tcp on provider machine 0", self.output.getvalue()) self.assertIn("Opened 443/tcp on provider machine 0", self.output.getvalue()) # Now change port setup yield wordpress_0.open_port(8080, "tcp") yield wordpress_0.close_port(443, "tcp") yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set([(80, "tcp"), (8080, "tcp")])) self.assertIn("Opened 8080/tcp on provider machine 0", self.output.getvalue()) self.assertIn("Closed 443/tcp on provider machine 0", self.output.getvalue())
def test_process_machines_non_concurrency(self): """ Process machines should only be executed serially by an agent. """ manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() machine_state1 = yield manager.add_machine_state() call_1 = self.agent.process_machines([machine_state0.id]) # The second call should return immediately due to the # instance attribute guard. call_2 = self.agent.process_machines([machine_state1.id]) self.assertEqual(call_2.called, True) self.assertEqual(call_2.result, False) # The first call should have started a provider machine yield call_1 machines = yield self.agent.provider.get_machines() self.assertEquals(len(machines), 1) instance_id_0 = yield machine_state0.get_instance_id() self.assertEqual(instance_id_0, 0) instance_id_1 = yield machine_state1.get_instance_id() self.assertEqual(instance_id_1, None)
def setUp(self): yield super(ControlDebugHookTest, self).setUp() config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer"}}} self.write_config(yaml.dump(config)) self.config.load() self.environment = self.config.get_default() self.provider = self.environment.get_machine_provider() # Setup a machine in the provider self.provider_machine = (yield self.provider.start_machine( {"machine-id": 0, "dns-name": "antigravity.example.com"}))[0] # Setup the zk tree with a service, unit, and machine. self.service = yield self.add_service_from_charm("mysql") self.unit = yield self.service.add_unit_state() self.machine_manager = MachineStateManager(self.client) self.machine = yield self.machine_manager.add_machine_state() yield self.machine.set_instance_id(0) yield self.unit.assign_to_machine(self.machine) # capture the output. self.output = self.capture_logging( "juju.control.cli", level=logging.INFO) self.stderr = self.capture_stream("stderr") self.setup_exit(0)
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, "")
def test_open_close_ports_on_machine_not_yet_provided(self): """Verify that opening/closing ports will eventually succeed once a machine is provided. """ manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() wordpress = yield self.add_service("wordpress") yield wordpress.set_exposed_flag() wordpress_0 = yield wordpress.add_unit_state() yield wordpress_0.open_port(80, "tcp") yield wordpress_0.open_port(443, "tcp") yield wordpress_0.assign_to_machine(machine) # First attempt to open ports quietly fails (except for # logging) because the machine has not yet been provisioned yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertIn("No provisioned machine for machine 0", self.output.getvalue()) yield self.provide_machine(machine) # Machine is now provisioned (normally visible in the # provisioning agent through periodic rescan and corresponding # watches) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set([(80, "tcp"), (443, "tcp")]))
def __init__(self, client, is_running, provider): """Initialize a Firewall Manager. :param client: A connected zookeeper client. :param is_running: A function (usually a bound method) that returns whether the associated agent is still running or not. :param provider: A machine provider, used for making the actual changes in the environment to firewall settings. """ self.machine_state_manager = MachineStateManager(client) self.service_state_manager = ServiceStateManager(client) self.is_running = is_running self.provider = provider # Track all currently watched machines, using machine ID. self._watched_machines = set() # Map service name to either NotExposed or set of exposed unit names. # If a service name is present in the dictionary, it means its # respective expose node is being watched. self._watched_services = {} # Machines to retry open_close_ports because of earlier errors self._retry_machines_on_port_error = set() # Registration of observers for corresponding actions self._open_close_ports_observers = set() self._open_close_ports_on_machine_observers = set()
def setUp(self): yield super(ControlRemoveUnitTest, self).setUp() config = {"environments": {"firstenv": {"type": "dummy"}}} self.write_config(dump(config)) self.config.load() self.environment = self.config.get_default() self.provider = self.environment.get_machine_provider() # Setup some service units. self.service_state1 = yield self.add_service_from_charm("mysql") self.service_unit1 = yield self.service_state1.add_unit_state() self.service_unit2 = yield self.service_state1.add_unit_state() self.service_unit3 = yield self.service_state1.add_unit_state() # Add an assigned machine to one of them. self.machine_manager = MachineStateManager(self.client) self.machine = yield self.machine_manager.add_machine_state() yield self.machine.set_instance_id(0) yield self.service_unit1.assign_to_machine(self.machine) # Setup a machine in the provider matching the assigned. self.provider_machine = yield self.provider.start_machine({ "machine-id": 0, "dns-name": "antigravity.example.com" }) self.output = self.capture_logging(level=logging.DEBUG) self.stderr = self.capture_stream("stderr")
def test_transient_provider_error_on_get_machines(self): manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() mock_provider = self.mocker.patch(self.agent.provider) mock_provider.get_machines() self.mocker.result(fail(ProviderInteractionError())) mock_provider.get_machines() self.mocker.passthrough() self.mocker.replay() try: yield self.agent.process_machines([machine_state0.id]) except: self.fail("Should not raise") instance_id = yield machine_state0.get_instance_id() self.assertEqual(instance_id, None) yield self.agent.process_machines([machine_state0.id]) instance_id = yield machine_state0.get_instance_id() self.assertEqual(instance_id, 0) self.assertIn("Cannot get machine list", self.output.getvalue())
def test_watch_machine_changes_ignores_running_machine(self): """ If there is an existing machine instance and state, when a new machine state is added, the existing instance is preserved, and a new instance is created. """ manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() machines = yield self.agent.provider.start_machine( {"machine-id": machine_state0.id}) machine = machines.pop() yield machine_state0.set_instance_id(machine.instance_id) machine_state1 = yield manager.add_machine_state() machines = yield self.agent.provider.get_machines() self.assertEquals(len(machines), 1) yield self.agent.watch_machine_changes( None, [machine_state0.id, machine_state1.id]) machines = yield self.agent.provider.get_machines() self.assertEquals(len(machines), 2) instance_id = yield machine_state1.get_instance_id() self.assertEqual(instance_id, 1)
def test_open_close_ports_on_unassigned_machine(self): """Verify corner case that nothing happens on an unassigned machine.""" manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() yield self.provide_machine(machine) yield self.firewall_manager.process_machine(machine) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set())
def get_agent_config(self): # gets invoked by AgentTestBase.setUp options = yield super(MachineAgentTest, self).get_agent_config() machine_state_manager = MachineStateManager(self.client) self.machine_state = yield machine_state_manager.add_machine_state() self.change_environment(JUJU_MACHINE_ID="0", JUJU_HOME=self.juju_directory) options["machine_id"] = str(self.machine_state.id) # Start the agent with watching enabled returnValue(options)
def get_ip_address_for_machine(client, provider, machine_id): """Returns public DNS name and machine state for the machine id. :param client: a connected zookeeper client. :param provider: the `MachineProvider` in charge of the juju. :param machine_id: machine ID of the desired machine to connect to. :return: tuple of the DNS name and a `MachineState`. """ manager = MachineStateManager(client) machine_state = yield manager.get_machine_state(machine_id) instance_id = yield machine_state.get_instance_id() provider_machine = yield provider.get_machine(instance_id) returnValue((provider_machine.dns_name, machine_state))
def test_transient_unhandled_error_in_process_machines(self): """Verify that watch_machine_changes handles the exception. Provider implementations may use libraries like txaws that do not handle every error. However, this should not stop the watch from re-establishing itself, as will be the case if the exception is not caught. """ manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() machine_state1 = yield manager.add_machine_state() # Simulate a failure scenario seen occasionally when working # with OpenStack and txaws mock_agent = self.mocker.patch(self.agent) # Simulate transient error mock_agent.process_machines([machine_state0.id]) self.mocker.result(fail( TypeError("'NoneType' object is not iterable"))) # Let it succeed on second try. In this case, the scenario is # that the watch triggered before the periodic_machine_check # was run again mock_agent.process_machines([machine_state0.id, machine_state1.id]) self.mocker.passthrough() self.mocker.replay() # Verify that watch_machine_changes does not fail even in the case of # the transient error, although no work was done try: yield self.agent.watch_machine_changes([], [machine_state0.id]) except: self.fail("Should not raise") instance_id = yield machine_state0.get_instance_id() self.assertEqual(instance_id, None) # Second attempt, verifiy it did in fact process the machine yield self.agent.watch_machine_changes( [machine_state0.id], [machine_state0.id, machine_state1.id]) self.assertEqual((yield machine_state0.get_instance_id()), 0) self.assertEqual((yield machine_state1.get_instance_id()), 1) # But only after attempting and failing the first time self.assertIn( "Got unexpected exception in processing machines, will retry", self.output.getvalue()) self.assertIn("'NoneType' object is not iterable", self.output.getvalue())
def test_open_close_ports_on_machine_will_retry(self): """Verify port mgmt for a machine will retry if there's a failure.""" mock_provider = self.mocker.patch(MachineProvider) mock_provider.open_port(MATCH_MACHINE, 0, 80, "tcp") self.mocker.result(fail( TypeError("'NoneType' object is not iterable"))) mock_provider.open_port(MATCH_MACHINE, 0, 80, "tcp") self.mocker.result( fail(ProviderInteractionError("Some sort of EC2 problem"))) mock_provider.open_port(MATCH_MACHINE, 0, 80, "tcp") self.mocker.passthrough() self.mocker.replay() manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() yield self.provide_machine(machine) # Expose a service and attempt to open/close ports. The first # attempt will see the simulated failure. wordpress = yield self.add_service("wordpress") yield wordpress.set_exposed_flag() wordpress_0 = yield wordpress.add_unit_state() yield wordpress_0.assign_to_machine(machine) yield self.firewall_manager.process_machine(machine) yield wordpress_0.open_port(80, "tcp") yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set()) self.assertIn("Got exception in opening/closing ports, will retry", self.output.getvalue()) self.assertIn("TypeError: 'NoneType' object is not iterable", self.output.getvalue()) # Retries will now happen in the periodic recheck. First one # still fails due to simulated error. yield self.firewall_manager.process_machine(machine) self.assertEqual((yield self.get_provider_ports(machine)), set()) self.assertIn("ProviderInteractionError: Some sort of EC2 problem", self.output.getvalue()) # Third time is the charm in the mock setup, the recheck succeeds yield self.firewall_manager.process_machine(machine) self.assertEqual((yield self.get_provider_ports(machine)), set([(80, "tcp")])) self.assertIn("Opened 80/tcp on provider machine 0", self.output.getvalue())
def test_machine_state_reflects_invalid_provider_state(self): """ If a machine state has an invalid instance_id, it should be detected, and a new machine started and the machine state updated with the new instance_id. """ machine_manager = MachineStateManager(self.client) m1 = yield machine_manager.add_machine_state() yield m1.set_instance_id("zebra") m2 = yield machine_manager.add_machine_state() yield self.agent.watch_machine_changes(None, [m1.id, m2.id]) m1_instance_id = yield m1.get_instance_id() self.assertEqual(m1_instance_id, 0) m2_instance_id = yield m2.get_instance_id() self.assertEqual(m2_instance_id, 1)
def test_observe_open_close_ports_on_machine(self): """Verify one or more observers can be established on action.""" manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() # Add one observer, verify it gets called calls = [set()] self.firewall_manager.add_open_close_ports_on_machine_observer( Observer(calls, "a")) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual(calls[0], set([("a", machine.id)])) # Reset records of calls, and then add a second observer. # Verify both get called. calls[0] = set() self.firewall_manager.add_open_close_ports_on_machine_observer( Observer(calls, "b")) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual(calls[0], set([("a", machine.id), ("b", machine.id)]))
def test_start_agent_with_watch(self): mock_reactor = self.mocker.patch(reactor) mock_reactor.callLater(self.agent.machine_check_period, self.agent.periodic_machine_check) self.mocker.replay() self.agent.set_watch_enabled(True) yield self.agent.start() manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() exists_d, watch_d = self.client.exists_and_watch( "/machines/%s" % machine_state0.internal_id) yield exists_d # Wait for the provisioning agent to wake and modify # the machine id. yield watch_d instance_id = yield machine_state0.get_instance_id() self.assertEqual(instance_id, 0)
def test_process_machine_is_called(self): """Verify FirewallManager is called when machines are processed""" from juju.state.firewall import FirewallManager mock_manager = self.mocker.patch(FirewallManager) seen = [] def record_machine(machine): seen.append(machine) return succeed(True) mock_manager.process_machine(MATCH_MACHINE_STATE) self.mocker.call(record_machine) self.mocker.replay() machine_manager = MachineStateManager(self.client) machine_state = yield machine_manager.add_machine_state() yield self.agent.process_machines([machine_state.id]) self.assertEqual(seen, [machine_state])
def test_initialize(self): yield self.layout.initialize() yield self.assert_existence_and_acl("/charms") yield self.assert_existence_and_acl("/services") yield self.assert_existence_and_acl("/units") yield self.assert_existence_and_acl("/machines") yield self.assert_existence_and_acl("/relations") yield self.assert_existence_and_acl("/initialized") machine_state_manager = MachineStateManager(self.client) machine_state = yield machine_state_manager.get_machine_state(0) self.assertTrue(machine_state) instance_id = yield machine_state.get_instance_id() self.assertEqual(instance_id, "i-abcdef") settings_manager = GlobalSettingsStateManager(self.client) self.assertEqual((yield settings_manager.get_provider_type()), "dummy") self.assertEqual(self.log.getvalue().strip(), "Initializing zookeeper hierarchy")
def test_process_machine_ignores_stop_watcher(self): """Verify that process machine catches `StopWatcher`. `process_machine` calls `open_close_ports_on_machine`, which as verified in an earlier test, raises a `StopWatcher` exception to shutdown watches that use it in the event of agent shutdown. Verify this dual usage does not cause issues while the agent is being stopped for this usage. """ mock_provider = self.mocker.patch(MachineProvider) mock_provider.open_port(MATCH_MACHINE, 0, 80, "tcp") self.mocker.result(fail( TypeError("'NoneType' object is not iterable"))) self.mocker.replay() manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() yield self.provide_machine(machine) # Expose a service and attempt to open/close ports. The first # attempt will see the simulated failure. wordpress = yield self.add_service("wordpress") yield wordpress.set_exposed_flag() wordpress_0 = yield wordpress.add_unit_state() yield wordpress_0.assign_to_machine(machine) yield self.firewall_manager.process_machine(machine) yield wordpress_0.open_port(80, "tcp") yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set()) self.assertIn("Got exception in opening/closing ports, will retry", self.output.getvalue()) self.assertIn("TypeError: 'NoneType' object is not iterable", self.output.getvalue()) # Stop the provisioning agent self.stop() # But retries can potentially still happening anyway, just # make certain nothing bad happens. yield self.firewall_manager.process_machine(machine)
def place_unit(client, policy_name, unit_state): """Return machine state of unit_states assignment. :param client: A connected zookeeper client. :param policy_name: The name of the unit placement policy. :param unit_state: The unit to be assigned. :param provider_type: The type of the machine environment provider. """ machine_state_manager = MachineStateManager(client) # default policy handling if policy_name is None: placement = _unassigned_placement else: placement = _PLACEMENT_LOOKUP.get(policy_name) if placement is None: # We should never get here, pick policy should always pick valid. raise JujuError("Invalid policy:%r for provider" % policy_name) return placement(client, machine_state_manager, unit_state)
def test_watch_machine_changes_processes_new_machine_id(self): """The agent should process a new machine id by creating it""" manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() machine_state1 = yield manager.add_machine_state() yield self.agent.watch_machine_changes( None, [machine_state0.id, machine_state1.id]) self.assertIn("Machines changed old:None new:[0, 1]", self.output.getvalue()) self.assertIn("Starting machine id:0", self.output.getvalue()) machines = yield self.agent.provider.get_machines() self.assertEquals(len(machines), 2) instance_id = yield machine_state0.get_instance_id() self.assertEqual(instance_id, 0) instance_id = yield machine_state1.get_instance_id() self.assertEqual(instance_id, 1)
def test_open_close_ports_on_machine_unexposed_service(self): """Verify opening/closing ports on a machine works properly. In particular this is done without watch support.""" manager = MachineStateManager(self.client) machine = yield manager.add_machine_state() yield self.provide_machine(machine) wordpress = yield self.add_service("wordpress") wordpress_0 = yield wordpress.add_unit_state() # Port activity, but service is not exposed yield wordpress_0.open_port(80, "tcp") yield wordpress_0.open_port(443, "tcp") yield wordpress_0.assign_to_machine(machine) yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set()) # Now expose it yield wordpress.set_exposed_flag() yield self.firewall_manager.open_close_ports_on_machine(machine.id) self.assertEqual((yield self.get_provider_ports(machine)), set([(80, "tcp"), (443, "tcp")]))
def start(self): """Start the machine agent. Creates state directories on the machine, retrieves the machine state, and enables watch on assigned units. """ # Initialize directory paths. if not os.path.exists(self.charms_directory): os.makedirs(self.charms_directory) 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.charm_state_manager = CharmStateManager(self.client) # 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) # Find out what provided the machine, and how to deploy units. settings = GlobalSettingsStateManager(self.client) self.provider_type = yield settings.get_provider_type() self.deploy_factory = get_deploy_factory(self.provider_type) # Connect the machine agent, broadcasting presence to the world. yield self.machine_state.connect_agent() log.info( "Machine agent started id:%s deploy:%r provider:%r" % (self.get_machine_id(), self.deploy_factory, self.provider_type))
def test_transient_provider_error_on_start_machine(self): """ If there's an error when processing changes, the agent should log the error and continue. """ manager = MachineStateManager(self.client) machine_state0 = yield manager.add_machine_state() machine_state1 = yield manager.add_machine_state() mock_provider = self.mocker.patch(self.agent.provider) mock_provider.start_machine({"machine-id": 0}) self.mocker.result(fail(ProviderInteractionError())) mock_provider.start_machine({"machine-id": 1}) self.mocker.passthrough() self.mocker.replay() yield self.agent.watch_machine_changes( [], [machine_state0.id, machine_state1.id]) machine1_instance_id = yield machine_state1.get_instance_id() self.assertEqual(machine1_instance_id, 0) self.assertIn("Cannot process machine 0", self.output.getvalue())
def test_watches_trigger_port_mgmt(self): """Verify that watches properly trigger firewall management for the corresponding service units on the corresponding machines. """ self.start() manager = MachineStateManager(self.client) # Immediately expose drupal = yield self.add_service("drupal") wordpress = yield self.add_service("wordpress") yield drupal.set_exposed_flag() yield wordpress.set_exposed_flag() # Then add these units drupal_0 = yield drupal.add_unit_state() wordpress_0 = yield wordpress.add_unit_state() wordpress_1 = yield wordpress.add_unit_state() wordpress_2 = yield wordpress.add_unit_state() # Assign some machines; in particular verify that multiple # service units on one machine works properly with opening # firewall machine_0 = yield manager.add_machine_state() machine_1 = yield manager.add_machine_state() machine_2 = yield manager.add_machine_state() yield self.provide_machine(machine_0) yield self.provide_machine(machine_1) yield self.provide_machine(machine_2) yield drupal_0.assign_to_machine(machine_0) yield wordpress_0.assign_to_machine(machine_0) yield wordpress_1.assign_to_machine(machine_1) yield wordpress_2.assign_to_machine(machine_2) # Simulate service units opening ports expected_machines = self.wait_on_expected_machines(set([0, 1])) expected_units = self.wait_on_expected_units( set(["wordpress/0", "wordpress/1", "drupal/0"])) yield drupal_0.open_port(8080, "tcp") yield drupal_0.open_port(443, "tcp") yield wordpress_0.open_port(80, "tcp") yield wordpress_1.open_port(80, "tcp") self.assertTrue((yield expected_units)) self.assertTrue((yield expected_machines)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(80, "tcp"), (443, "tcp"), (8080, "tcp")])) self.assertEqual((yield self.get_provider_ports(machine_1)), set([(80, "tcp")])) # Simulate service units close port expected_machines = self.wait_on_expected_machines(set([1, 2])) yield wordpress_1.close_port(80, "tcp") yield wordpress_2.open_port(80, "tcp") self.assertTrue((yield expected_machines)) self.assertEqual((yield self.get_provider_ports(machine_1)), set()) # Simulate service units open port expected_machines = self.wait_on_expected_machines(set([0])) yield wordpress_0.open_port(53, "udp") self.assertTrue((yield expected_machines)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(53, "udp"), (80, "tcp"), (443, "tcp"), (8080, "tcp")])) self.stop()
def test_late_expose_properly_triggers(self): """Verify that an expose flag properly cascades the corresponding watches to perform the desired firewall mgmt. """ self.start() manager = MachineStateManager(self.client) drupal = yield self.add_service("drupal") wordpress = yield self.add_service("wordpress") # Then add these units drupal_0 = yield drupal.add_unit_state() wordpress_0 = yield wordpress.add_unit_state() wordpress_1 = yield wordpress.add_unit_state() machine_0 = yield manager.add_machine_state() machine_1 = yield manager.add_machine_state() yield self.provide_machine(machine_0) yield self.provide_machine(machine_1) yield drupal_0.assign_to_machine(machine_0) yield wordpress_0.assign_to_machine(machine_0) yield wordpress_1.assign_to_machine(machine_1) # Simulate service units opening ports expected_machines = self.wait_on_expected_machines(set([0, 1])) expected_units = self.wait_on_expected_units( set(["wordpress/0", "wordpress/1"])) yield drupal_0.open_port(8080, "tcp") yield drupal_0.open_port(443, "tcp") yield wordpress_0.open_port(80, "tcp") yield wordpress_1.open_port(80, "tcp") yield wordpress.set_exposed_flag() self.assertTrue((yield expected_units)) self.assertTrue((yield expected_machines)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(80, "tcp")])) self.assertEqual((yield self.get_provider_ports(machine_1)), set([(80, "tcp")])) # Expose drupal service, verify ports are opened on provider expected_machines = self.wait_on_expected_machines(set([0])) expected_units = self.wait_on_expected_units(set(["drupal/0"])) yield drupal.set_exposed_flag() self.assertTrue((yield expected_machines)) self.assertTrue((yield expected_units)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(80, "tcp"), (443, "tcp"), (8080, "tcp")])) # Unexpose drupal service, verify only wordpress ports are now opened expected_machines = self.wait_on_expected_machines(set([0])) expected_units = self.wait_on_expected_units(set(["drupal/0"])) yield drupal.clear_exposed_flag() self.assertTrue((yield expected_machines)) self.assertTrue((yield expected_units)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(80, "tcp")])) # Re-expose drupal service, verify ports are once again opened expected_machines = self.wait_on_expected_machines(set([0])) expected_units = self.wait_on_expected_units(set(["drupal/0"])) yield drupal.set_exposed_flag() self.assertTrue((yield expected_machines)) self.assertTrue((yield expected_units)) self.assertEqual((yield self.get_provider_ports(machine_0)), set([(80, "tcp"), (443, "tcp"), (8080, "tcp")])) self.stop()
def collect(scope, machine_provider, client, log): """Extract status information into nested dicts for rendering. `scope`: an optional list of name specifiers. Globbing based wildcards supported. Defaults to all units, services and relations. `machine_provider`: machine provider for the environment `client`: ZK client connection `log`: a Python stdlib logger. """ service_manager = ServiceStateManager(client) relation_manager = RelationStateManager(client) machine_manager = MachineStateManager(client) charm_manager = CharmStateManager(client) service_data = {} machine_data = {} state = dict(services=service_data, machines=machine_data) seen_machines = set() filter_services, filter_units = digest_scope(scope) services = yield service_manager.get_all_service_states() for service in services: if len(filter_services): found = False for filter_service in filter_services: if fnmatch(service.service_name, filter_service): found = True break if not found: continue unit_data = {} relation_data = {} charm_id = yield service.get_charm_id() charm = yield charm_manager.get_charm_state(charm_id) service_data[service.service_name] = dict(units=unit_data, charm=charm.id, relations=relation_data) exposed = yield service.get_exposed_flag() if exposed: service_data[service.service_name].update(exposed=exposed) units = yield service.get_all_unit_states() unit_matched = False relations = yield relation_manager.get_relations_for_service(service) for unit in units: if len(filter_units): found = False for filter_unit in filter_units: if fnmatch(unit.unit_name, filter_unit): found = True break if not found: continue u = unit_data[unit.unit_name] = dict() machine_id = yield unit.get_assigned_machine_id() u["machine"] = machine_id unit_workflow_client = WorkflowStateClient(client, unit) unit_state = yield unit_workflow_client.get_state() if not unit_state: u["state"] = "pending" else: unit_connected = yield unit.has_agent() u["state"] = unit_state if unit_connected else "down" if exposed: open_ports = yield unit.get_open_ports() u["open-ports"] = [ "{port}/{proto}".format(**port_info) for port_info in open_ports ] u["public-address"] = yield unit.get_public_address() # indicate we should include information about this # machine later seen_machines.add(machine_id) unit_matched = True # collect info on each relation for the service unit relation_status = {} for relation in relations: try: relation_unit = yield relation.get_unit_state(unit) except UnitRelationStateNotFound: # This exception will occur when relations are # established between services without service # units, and therefore never have any # corresponding service relation units. This # scenario does not occur in actual deployments, # but can happen in test circumstances. In # particular, it will happen with a misconfigured # provider, which exercises this codepath. continue # should not occur, but status should not fail relation_workflow_client = WorkflowStateClient( client, relation_unit) relation_workflow_state = \ yield relation_workflow_client.get_state() relation_status[relation.relation_name] = dict( state=relation_workflow_state) u["relations"] = relation_status # after filtering units check if any matched or remove the # service from the output if filter_units and not unit_matched: del service_data[service.service_name] continue for relation in relations: rel_services = yield relation.get_service_states() # A single related service implies a peer relation. More # imply a bi-directional provides/requires relationship. # In the later case we omit the local side of the relation # when reporting. if len(rel_services) > 1: # Filter out self from multi-service relations. rel_services = [ rsn for rsn in rel_services if rsn.service_name != service.service_name ] if len(rel_services) > 1: raise ValueError("Unexpected relationship with more " "than 2 endpoints") rel_service = rel_services[0] relation_data[relation.relation_name] = rel_service.service_name machines = yield machine_manager.get_all_machine_states() for machine_state in machines: if (filter_services or filter_units) and \ machine_state.id not in seen_machines: continue instance_id = yield machine_state.get_instance_id() m = {"instance-id": instance_id \ if instance_id is not None else "pending"} if instance_id is not None: try: pm = yield machine_provider.get_machine(instance_id) m["dns-name"] = pm.dns_name m["instance-state"] = pm.state if (yield machine_state.has_agent()): # if the agent's connected, we're fine m["state"] = "running" else: units = (yield machine_state.get_all_service_unit_states()) for unit in units: unit_workflow_client = WorkflowStateClient( client, unit) if (yield unit_workflow_client.get_state()): # for unit to have a state, its agent must have # run, which implies the machine agent must have # been running correctly at some point in the past m["state"] = "down" break else: # otherwise we're probably just still waiting m["state"] = "not-started" except ProviderError: # The provider doesn't have machine information log.error("Machine provider information missing: machine %s" % (machine_state.id)) machine_data[machine_state.id] = m returnValue(state)