def test_add_charm_with_concurrent_removal(self): """ If a charm is published, and it detects that the charm exists already exists, it will attempt to retrieve the charm state to verify there is no checksum mismatch. If concurrently the charm is removed, the publisher should fail with a statechange error. """ manager = self.mocker.patch(CharmStateManager) manager.get_charm_state(self.charm_id) self.mocker.passthrough() def match_charm_bundle(bundle): return isinstance(bundle, CharmBundle) def match_charm_url(url): return url.startswith("file://") manager.add_charm_state(self.charm_id, MATCH(match_charm_bundle), MATCH(match_charm_url)) self.mocker.result(fail(zookeeper.NodeExistsException())) manager.get_charm_state(self.charm_id) self.mocker.result(fail(zookeeper.NoNodeException())) self.mocker.replay() yield self.publisher.add_charm(self.charm_id, self.charm) yield self.failUnlessFailure(self.publisher.publish(), StateChanged)
def test_charm_download_http_error(self): """Errors in donwloading a charm are reported as charm not found. """ def match_string(expected, value): self.assertTrue(isinstance(value, basestring)) self.assertIn(expected, value) return True mock_storage = self.mocker.patch(self.storage) mock_storage.get_url( MATCH(partial(match_string, "local_3a_series_2f_dummy-1"))) remote_url = "http://example.com/foobar.zip" self.mocker.result(remote_url) download_page = self.mocker.replace(downloadPage) download_page( MATCH(partial(match_string, "http://example.com/foobar.zip")), MATCH(partial(match_string, "local_3a_series_2f_dummy-1"))) self.mocker.result(fail(Error("400", "Bad Stuff", ""))) self.mocker.replay() charm, charm_state = yield self.publish_charm() charm_directory = self.makeDir() self.assertEqual(charm_state.bundle_url, remote_url) error = yield self.assertFailure( download_charm(self.client, charm_state.id, charm_directory), FileNotFound) self.assertIn(remote_url, str(error))
def test_start(self): network_list_mock = self.mocker.mock() network_start_mock = self.mocker.mock() def check_definition(args): cmd, subcommand, template_path = args network_definition = open(template_path).read() self.assertIn('<name>foobar</name>', network_definition) self.assertIn('<bridge name="vbr-foobar-%d" />', network_definition) self.assertIn( '<range start="192.168.131.2" end="192.168.131.254" />', network_definition) self.assertIn( '<ip address="192.168.131.1" netmask="255.255.255.0">', network_definition) return True self.patch(subprocess, "check_output", network_list_mock) self.patch(subprocess, "check_call", network_start_mock) network_list_mock(["virsh", "net-list", "--all"], env={"LC_ALL": "C"}) self.mocker.result(network_list_default_output) network_start_mock(MATCH(check_definition)) network_start_mock(["virsh", "net-start", "foobar"]) self.mocker.replay() network = Network("foobar") yield network.start()
def test_destroy_environment(self): """Command will terminate instances in only one environment.""" config = { "environments": {"firstenv": {"type": "dummy"}, "secondenv": {"type": "dummy"}}} self.write_config(dump(config)) finished = self.setup_cli_reactor() envs = set(("firstenv", "secondenv")) def track_destroy_environment_call(self): envs.remove(self.environment_name) return succeed(True) provider = self.mocker.patch(MachineProvider) provider.destroy_environment() self.mocker.call(track_destroy_environment_call, with_object=True) self.setup_exit(0) mock_raw = self.mocker.replace(raw_input) mock_raw(MATCH(lambda x: x.startswith( "WARNING: this command will destroy the 'secondenv' " "environment (type: dummy)"))) self.mocker.result("y") self.mocker.replay() main(["destroy-environment", "-e", "secondenv"]) yield finished self.assertIn("Destroying environment 'secondenv' (type: dummy)...", self.log.getvalue()) self.assertEqual(envs, set(["firstenv"]))
def test_deploy_with_environment_specified(self): self.setup_cli_reactor() self.setup_exit(0) command = self.mocker.replace("juju.control.deploy.deploy") config = { "environments": { "firstenv": { "type": "dummy", "admin-secret": "homer" }, "secondenv": { "type": "dummy", "admin-secret": "marge" } } } self.write_config(yaml.dump(config)) def match_config(config): return isinstance(config, EnvironmentsConfig) def match_environment(environment): return isinstance(environment, Environment) and \ environment.name == "secondenv" def match_anything(*args): print args return True command(MATCH(match_config), MATCH(match_environment), self.unbundled_repo_path, "local:sample", None, MATCH(lambda x: isinstance(x, logging.Logger)), None, num_units=1) self.mocker.replay() self.mocker.result(succeed(True)) self.capture_stream("stderr") main([ "deploy", "--environment", "secondenv", "--repository", self.unbundled_repo_path, "local:sample" ])
def _mock_save(self): """Mock saving bootstrap instances to S3.""" def match_string(data): return isinstance(data, str) self.s3.put_object( self.env_name, "provider-state", MATCH(match_string)) self.mocker.result(succeed(True))
def wb_test_start_with_relation_errors(self): """ White box testing to ensure that an error when starting the lifecycle is propogated appropriately, and that we collect all results before returning. """ mock_service = self.mocker.patch(self.lifecycle._service) mock_service.watch_relation_states(MATCH(lambda x: callable(x))) self.mocker.result(fail(SyntaxError())) mock_unit = self.mocker.patch(self.lifecycle._unit) mock_unit.watch_relation_resolved(MATCH(lambda x: callable(x))) results = [] wait = Deferred() @inlineCallbacks def complete(*args): yield wait results.append(True) returnValue(True) self.mocker.call(complete) self.mocker.replay() # Start the unit, assert a failure, and capture the deferred wait_failure = self.assertFailure(self.lifecycle.start(), SyntaxError) # Verify we have no results for the second callback or the start call self.assertFalse(results) self.assertFalse(wait_failure.called) # Let the second callback complete wait.callback(True) # Wait for the start error to bubble up. yield wait_failure # Verify the second deferred was waited on. self.assertTrue(results)
def test_save_error(self): class SomeError(Exception): pass put = self.mocker.mock() put("provider-state", MATCH(self.is_expected_yaml)) self.mocker.result(fail(SomeError("blah"))) self.mocker.replay() provider = DummyProvider(put=put) save_state = SaveState(provider) d = save_state.run({"some": "thing"}) self.assertFailure(d, SomeError) return d
def test_save(self): put = self.mocker.mock() put("provider-state", MATCH(self.is_expected_yaml)) self.mocker.result(succeed(True)) self.mocker.replay() provider = DummyProvider(put=put) save_state = SaveState(provider) d = save_state.run({"some": "thing"}) def verify_true(result): self.assertEquals(result, True) d.addCallback(verify_true) return d
def test_agent_logger(self): parser = argparse.ArgumentParser() BaseAgent.setup_options(parser) log_file_path = self.makeFile() options = parser.parse_args(["--logfile", log_file_path], namespace=TwistedOptionNamespace()) def match_observer(observer): return isinstance(observer.im_self, log.PythonLoggingObserver) def cleanup(observer): # post test cleanup of global state. log.removeObserver(observer) logging.getLogger().handlers = [] original_log_with_observer = log.startLoggingWithObserver def _start_log_with_observer(observer): self.addCleanup(cleanup, observer) # by default logging will replace stdout/stderr return original_log_with_observer(observer, 0) app = self.mocker.mock() app.getComponent(log.ILogObserver, None) self.mocker.result(None) start_log_with_observer = self.mocker.replace( log.startLoggingWithObserver) start_log_with_observer(MATCH(match_observer)) self.mocker.call(_start_log_with_observer) self.mocker.replay() agent_logger = AgentLogger(options) agent_logger.start(app) # We suppress twisted messages below the error level. output = open(log_file_path).read() self.assertFalse(output) # also verify we didn't mess with the app logging. app_log = logging.getLogger() app_log.info("Good") # and that twisted errors still go through. log.err("Something bad happened") output = open(log_file_path).read() self.assertIn("Good", output) self.assertIn("Something bad happened", output)
def test_charm_download_http(self): """Downloading a charm should store the charm locally. """ mock_storage = self.mocker.patch(self.storage) def match_string(expected, value): self.assertTrue(isinstance(value, basestring)) self.assertIn(expected, value) return True mock_storage.get_url( MATCH(partial(match_string, "local_3a_series_2f_dummy-1"))) self.mocker.result("http://example.com/foobar.zip") download_page = self.mocker.replace(downloadPage) download_page( MATCH(partial(match_string, "http://example.com/foobar.zip")), MATCH(partial(match_string, "local_3a_series_2f_dummy-1"))) def bundle_in_place(url, local_path): # must keep ref to charm else temp file goes out of scope. charm = get_charm_from_path(sample_directory) bundle = charm.as_bundle() shutil.copyfile(bundle.path, local_path) self.mocker.call(bundle_in_place) self.mocker.result(succeed(True)) self.mocker.replay() charm, charm_state = yield self.publish_charm() charm_directory = self.makeDir() self.assertEqual(charm_state.bundle_url, "http://example.com/foobar.zip") # Download the charm yield download_charm(self.client, charm_state.id, charm_directory)
def _mock_launch(self): """Mock launching a bootstrap machine on ec2.""" def verify_user_data(data): expect_path = os.path.join(DATA_DIR, "bootstrap_cloud_init") with open(expect_path) as f: expect_cloud_init = yaml.load(f.read()) self.assertEquals(yaml.load(data), expect_cloud_init) return True self.ec2.run_instances( image_id="ami-default", instance_type="m1.small", max_count=1, min_count=1, security_groups=["juju-moon", "juju-moon-0"], user_data=MATCH(verify_user_data))
def test_hanging_hook(self): """Verify that a hook that's slow to end is terminated. Test this by having the hook fork a process that hangs around for a while, necessitating reaping. This happens because the child process does not close the parent's file descriptors (as expected with daemonization, for example). http://www.snailbook.com/faq/background-jobs.auto.html provides some insight into what can happen. """ from twisted.internet import reactor # Ordinarily the reaper for any such hanging hooks will run in # 5s, but we are impatient. Force it to end much sooner by # intercepting the reaper setup. mock_reactor = self.mocker.patch(reactor) # Although we can match precisely on the # Process.loseConnection, Mocker gets confused with also # trying to match the delay time, using something like # `MATCH(lambda x: isinstance(x, (int, float)))`. So instead # we hardcode it here as just 5. mock_reactor.callLater(5, MATCH(lambda x: isinstance(x.im_self, Process))) def intercept_reaper_setup(delay, reaper): # Given this is an external process, let's sleep for a # short period of time return reactor.callLater(0.2, reaper) self.mocker.call(intercept_reaper_setup) self.mocker.replay() # The hook script will immediately exit with a status code of # 0, but it created a child process (via shell backgrounding) # that is running (and will sleep for >10s) exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) result = yield exe(self.get_test_hook("hanging-hook")) self.assertEqual(result, 0) # Verify after waiting for the process to close (which means # the reaper ran!), we get output for the first phase of the # hanging hook, but not after its second, more extended sleep. yield exe.ended self.assertIn("Slept for 50ms", self.log.getvalue()) self.assertNotIn("Slept for 1s", self.log.getvalue())
def test_handler_error(self): """An error in the handler gets reported to stderr.""" self.error_stream = self.capture_stream("stderr") mock_client = self.mocker.patch(self.client) mock_client.create("/logs/log-", MATCH(lambda x: isinstance(x, str)), flags=zookeeper.SEQUENCE) self.mocker.result(fail(zookeeper.NoNodeException())) self.mocker.replay() log = yield self.get_configured_log() log.info("something interesting") # assert the log entry doesn't exist exists = yield self.client.exists("/logs/log-%010d" % 0) self.assertFalse(exists) # assert the error made it to stderr self.assertIn("NoNodeException", self.error_stream.getvalue())
def _mock_launch(self, instance, expect_ami="ami-default", expect_instance_type="m1.small", cloud_init="launch_cloud_init"): def verify_user_data(data): expect_path = os.path.join(DATA_DIR, cloud_init) with open(expect_path) as f: expect_cloud_init = yaml.load(f.read()) self.assertEquals(yaml.load(data), expect_cloud_init) return True self.ec2.run_instances(image_id=expect_ami, instance_type=expect_instance_type, max_count=1, min_count=1, security_groups=["juju-moon", "juju-moon-1"], user_data=MATCH(verify_user_data)) self.mocker.result(succeed([instance]))
def test_destroy_environment_prompt_no(self): """If a user returns no to the prompt, destroy-environment is aborted. """ config = { "environments": {"firstenv": {"type": "dummy"}, "secondenv": {"type": "dummy"}}} self.write_config(dump(config)) finished = self.setup_cli_reactor() self.setup_exit(0) mock_raw = self.mocker.replace(raw_input) mock_raw(MATCH(lambda x: x.startswith( "WARNING: this command will destroy the 'secondenv' " "environment (type: dummy)"))) self.mocker.result("n") self.mocker.replay() main(["destroy-environment", "-e", "secondenv"]) yield finished self.assertIn( "Environment destruction aborted", self.log.getvalue())
def _mock_launch_utils(self, ami_name="ami-default", **get_ami_kwargs): get_public_key = self.mocker.replace( "juju.providers.common.utils.get_user_authorized_keys") def match_config(arg): return isinstance(arg, dict) get_public_key(MATCH(match_config)) self.mocker.result("zebra") if not get_ami_kwargs: return get_ami = self.mocker.replace( "juju.providers.ec2.utils.get_current_ami") get_ami(KWARGS) def check_kwargs(**kwargs): self.assertEquals(kwargs, get_ami_kwargs) return succeed(ami_name) self.mocker.call(check_kwargs)
def test_run(self): """Invokes the run class method on an agent. This will create an agent instance, parse the cli args, passes them to the agent, and starts the agent runner. """ self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address()) runner = self.mocker.patch(AgentRunner) runner.run() mock_agent = self.mocker.patch(BaseAgent) def match_args(config): self.assertEqual(config["zookeeper_servers"], get_test_zookeeper_address()) return True mock_agent.configure(MATCH(match_args)) self.mocker.passthrough() self.mocker.replay() BaseAgent.run()
def mock_start_system(self, verify_ks_meta, fail_modify=False, fail_save=False): self.proxy_m.callRemote("get_systems") self.mocker.result( succeed([{ "uid": "winston-uid", "mgmt_classes": ["acquired"], "ks_meta": {} }])), self.proxy_m.callRemote("find_system", {"uid": "winston-uid"}) self.mocker.result(succeed(["winston"])) self.proxy_m.callRemote("get_system_handle", "winston", "TOKEN") self.mocker.result(succeed("smith")) match_ks_meta = MATCH(verify_ks_meta) self.proxy_m.callRemote("modify_system", "smith", "ks_meta", match_ks_meta, "TOKEN") if fail_modify: self.mocker.result(succeed(False)) return self.mocker.result(succeed(True)) self.proxy_m.callRemote("modify_system", "smith", "netboot_enabled", True, "TOKEN") self.mocker.result(succeed(True)) self.proxy_m.callRemote("save_system", "smith", "TOKEN") if fail_save: self.mocker.result(succeed(False)) return self.mocker.result(succeed(True)) self.proxy_m.callRemote("background_power_system", { "power": "on", "systems": ["winston"] }, "TOKEN") self.mocker.result(succeed("[some-timestamp]_power"))
def test_get_file_unicode(self): """Retrieving a file with a unicode object, will refetch with a utf8 interpretation.""" content = "blah blah" control_bucket = self.get_config()["control-bucket"] def match_string(s): self.assertEqual( s, "\xe2\x99\xa3\xe2\x99\xa6\xe2\x99\xa5\xe2\x99\xa0.txt") self.assertFalse(isinstance(s, unicode)) return True self.s3.get_object(control_bucket, MATCH(match_string)) self.mocker.result(succeed(content)) self.mocker.replay() storage = self.get_storage() d = storage.get(u"\u2663\u2666\u2665\u2660.txt") def validate_result(result): self.assertEqual(result.read(), content) d.addCallback(validate_result) return d
from twisted.python import log import zookeeper from txzookeeper import ZookeeperClient from juju.lib.testing import TestCase from juju.lib.mocker import MATCH from juju.tests.common import get_test_zookeeper_address from juju.agents.base import (BaseAgent, TwistedOptionNamespace, AgentRunner, AgentLogger) from juju.errors import NoConnection, JujuError from juju.lib.zklog import ZookeeperHandler from juju.agents.tests.common import AgentTestBase MATCH_APP = MATCH(lambda x: isinstance(x, Componentized)) MATCH_HANDLER = MATCH(lambda x: isinstance(x, ZookeeperHandler)) class BaseAgentTest(TestCase): @inlineCallbacks def setUp(self): yield super(BaseAgentTest, self).setUp() self.change_environment(JUJU_HOME=self.makeDir()) def test_as_app(self): """The agent class can be accessed as an application.""" app = BaseAgent().as_app() multi_service = IService(app, None) self.assertTrue(IServiceCollection.providedBy(multi_service)) services = list(multi_service)
def test_lock_start_stop_watch(self): """The lifecycle, internally employs lock to prevent simulatenous execution of methods which modify internal state. This allows for a long running hook to be called safely, even if the other invocations of the lifecycle, the subsequent invocations will block till they can acquire the lock. """ self.write_hook("start", "#!/bin/bash\necho start\n") self.write_hook("stop", "#!/bin/bash\necho stop\n") results = [] finish_callback = [Deferred() for i in range(4)] # Control the speed of hook execution original_invoker = Invoker.__call__ invoker = self.mocker.patch(Invoker) @inlineCallbacks def long_hook(ctx, hook_path): results.append(os.path.basename(hook_path)) yield finish_callback[len(results) - 1] yield original_invoker(ctx, hook_path) for i in range(4): invoker(MATCH(lambda x: x.endswith("start") or x.endswith("stop"))) self.mocker.call(long_hook, with_object=True) self.mocker.replay() # Hook execution sequence to match on. test_complete = self.wait_on_hook(sequence=[ "config-changed", "start", "stop", "config-changed", "start" ]) # Fire off the lifecycle methods execution_callbacks = [ self.lifecycle.start(), self.lifecycle.stop(), self.lifecycle.start(), self.lifecycle.stop() ] self.assertEqual([0, 0, 0, 0], [x.called for x in execution_callbacks]) # kill the delay on the second finish_callback[1].callback(True) finish_callback[2].callback(True) self.assertEqual([0, 0, 0, 0], [x.called for x in execution_callbacks]) # let them pass, kill the delay on the first finish_callback[0].callback(True) yield test_complete self.assertEqual([False, True, True, False], [x.called for x in execution_callbacks]) # Finish the last hook finish_callback[3].callback(True) yield self.wait_on_hook("stop") self.assertEqual([True, True, True, True], [x.called for x in execution_callbacks])
from yaml import dump from twisted.internet.defer import fail, succeed from txaws.s3.client import S3Client from txaws.s3.exception import S3Error from txaws.ec2.client import EC2Client from txaws.ec2.exception import EC2Error from txaws.ec2.model import Instance, Reservation, SecurityGroup from juju.lib.mocker import KWARGS, MATCH from juju.providers.ec2 import MachineProvider from juju.providers.ec2.machine import EC2ProviderMachine MATCH_GROUP = MATCH(lambda x: x.startswith("juju-moon")) class EC2TestMixin(object): env_name = "moon" service_factory_kwargs = None def get_config(self): return {"type": "ec2", "juju-origin": "distro", "admin-secret": "magic-beans", "access-key": "0f62e973d5f8", "secret-key": "3e5a7c653f59", "control-bucket": self.env_name} def get_provider(self):
from juju.errors import JujuError from juju.charm.bundle import CharmBundle from juju.charm.directory import CharmDirectory from juju.charm.publisher import CharmPublisher from juju.charm.tests import local_charm_id from juju.charm.tests.test_repository import RepositoryTestBase from juju.lib import under from juju.lib.mocker import MATCH from juju.state.environment import EnvironmentStateManager from juju.state.machine import MachineStateManager, MachineState from juju.state.service import ServiceStateManager from juju.tests.common import get_test_zookeeper_address from .common import AgentTestBase MATCH_BUNDLE = MATCH(lambda x: isinstance(x, CharmBundle)) class MachineAgentTest(AgentTestBase, RepositoryTestBase): agent_class = MachineAgent @inlineCallbacks def setUp(self): yield super(MachineAgentTest, self).setUp() self.output = self.capture_logging("juju.agents.machine", level=logging.DEBUG) config = self.get_test_environment_config() environment = config.get_default()
from juju.agents.provision import ProvisioningAgent from juju.environment.environment import Environment from juju.environment.config import EnvironmentsConfig from juju.environment.errors import EnvironmentsConfigError from juju.environment.tests.test_config import SAMPLE_ENV from juju.errors import ProviderInteractionError from juju.lib.mocker import MATCH from juju.providers.dummy import DummyMachine from juju.state.errors import StopWatcher from juju.state.machine import MachineState, MachineStateManager from juju.state.tests.test_service import ServiceStateManagerTestBase from .common import AgentTestBase MATCH_MACHINE = MATCH(lambda x: isinstance(x, DummyMachine)) MATCH_MACHINE_STATE = MATCH(lambda x: isinstance(x, MachineState)) MATCH_SET = MATCH(lambda x: isinstance(x, set)) class ProvisioningTestBase(AgentTestBase): agent_class = ProvisioningAgent def get_serialized_environment(self): config = EnvironmentsConfig() config.parse(SAMPLE_ENV) return config.serialize("myfirstenv") class ProvisioningAgentStartupTest(ProvisioningTestBase):
import socket import time from twisted.internet.process import Process from twisted.internet.protocol import ProcessProtocol from twisted.internet.defer import succeed, fail, Deferred, inlineCallbacks from txzookeeper import ZookeeperClient from txzookeeper.client import ConnectionTimeoutException from juju.errors import NoConnection, InvalidHost, InvalidUser from juju.lib.mocker import MATCH, match_params from juju.lib.testing import TestCase from juju.state.sshclient import SSHClient MATCH_PROTOCOL = MATCH(lambda x: isinstance(x, ProcessProtocol)) MATCH_TIMEOUT = MATCH(lambda x: isinstance(x, (int, float))) MATCH_FUNC = MATCH(lambda x: callable(x)) MATCH_DEFERRED = MATCH(lambda x: isinstance(x, Deferred)) MATCH_PORT = MATCH(lambda x: isinstance(x, int)) def _match_localhost_port(value): if not ":" in value: return False host, port = value.split(":") if not port.isdigit(): return False return True
import sys from twisted.internet.protocol import ProcessProtocol from twisted.internet.defer import inlineCallbacks, succeed import juju from juju.charm import get_charm_from_path from juju.charm.tests.test_repository import RepositoryTestBase from juju.lib.lxc import LXCContainer from juju.lib.mocker import MATCH, ANY from juju.lib.twistutils import get_module_directory from juju.machine.unit import UnitMachineDeployment, UnitContainerDeployment from juju.machine.errors import UnitDeploymentError from juju.tests.common import get_test_zookeeper_address MATCH_PROTOCOL = MATCH(lambda x: isinstance(x, ProcessProtocol)) class UnitMachineDeploymentTest(RepositoryTestBase): def setUp(self): super(UnitMachineDeploymentTest, self).setUp() self.charm = get_charm_from_path(self.sample_dir1) self.bundle = self.charm.as_bundle() self.juju_directory = self.makeDir() self.units_directory = os.path.join(self.juju_directory, "units") os.mkdir(self.units_directory) self.unit_name = "wordpress/0" self.deployment = UnitMachineDeployment(self.unit_name, self.juju_directory)
import logging from twisted.internet.defer import (Deferred, inlineCallbacks, fail, returnValue, succeed) from juju.environment.config import EnvironmentsConfig from juju.errors import ProviderInteractionError from juju.lib.mocker import MATCH from juju.providers.dummy import DummyMachine, MachineProvider from juju.state.errors import StopWatcher from juju.state.firewall import FirewallManager from juju.state.machine import MachineStateManager from juju.state.service import ServiceStateManager from juju.state.tests.test_service import ServiceStateManagerTestBase MATCH_MACHINE = MATCH(lambda x: isinstance(x, DummyMachine)) SAMPLE_ENV = """\ environments: myfirstenv: type: dummy foo: bar storage-directory: %s """ class FirewallTestBase(ServiceStateManagerTestBase): @inlineCallbacks def setUp(self): yield super(FirewallTestBase, self).setUp() self._running = True