def _destroy_containers(self): container_map = yield get_containers(self._qualified_name) for container_name in container_map: container = LXCContainer(container_name, None, None, None) if container_map[container.container_name]: yield container.stop() yield container.destroy()
def __init__(self, unit_name, juju_home): super(UnitContainerDeployment, self).__init__(unit_name, juju_home) self._unit_namespace = os.environ.get("JUJU_UNIT_NS") self._juju_origin = os.environ.get("JUJU_ORIGIN") assert self._unit_namespace is not None, "Required unit ns not found" self.pid_file = None self.container = LXCContainer(self.container_name, None, None, None)
def test_container_clone(self): self.addCleanup(self.clean_container, DEFAULT_CONTAINER) self.addCleanup(self.clean_container, DEFAULT_CONTAINER + "_child") master_container = LXCContainer(DEFAULT_CONTAINER, origin="ppa", public_key="dsa...", series="oneiric") # verify that we cannot clone an unconstructed container failure = master_container.clone("test_lxc_fail") yield self.assertFailure(failure, LXCError) yield master_container.create() # Clone a child container from the template child_name = DEFAULT_CONTAINER + "_child" c = yield master_container.clone(child_name) self.assertEqual(c.container_name, child_name) running = yield c.is_running() self.assertFalse(running) yield c.run() running = yield c.is_running() self.assertTrue(running) output = _lxc_ls() self.assertIn(DEFAULT_CONTAINER, output) self.verify_container(c, "dsa...", "oneiric", "ppa") # verify that we are in containers containers = yield get_containers(None) self.assertEqual(containers[child_name], True) # tear it down yield c.destroy() running = yield c.is_running() self.assertFalse(running) containers = yield get_containers(None) self.assertNotIn(child_name, containers) # and its gone output = _lxc_ls() self.assertNotIn(child_name, output) yield master_container.destroy()
def _get_master_template(self, machine_id, zookeeper_hosts, public_key): container_template_name = "%s-%s-template" % ( self._unit_namespace, machine_id) master_template = LXCContainer( container_template_name, origin=self._juju_origin, public_key=public_key) # Debug log for the customize script, customize is only run on master. customize_log_path = os.path.join( self.juju_home, "units", "master-customize.log") master_template.customize_log = customize_log_path if not master_template.is_constructed(): log.debug("Creating master container...") yield master_template.create() log.debug("Created master container %s" % container_template_name) # it wasn't constructed and we couldn't construct it if not master_template.is_constructed(): raise LXCError("Unable to create master container") returnValue(master_template)
def __init__(self, unit_name, juju_home): self.unit_name = unit_name self.juju_home = juju_home self.unit_path_name = unit_name.replace("/", "-") self._juju_origin = os.environ.get("JUJU_ORIGIN") self._juju_series = os.environ.get("JUJU_SERIES") assert self._juju_series is not None, "Required juju series not found" self._unit_namespace = os.environ.get("JUJU_UNIT_NS") assert self._unit_namespace is not None, "Required unit ns not found" self.container_name = "%s-%s" % ( self._unit_namespace, self.unit_path_name) self.container = LXCContainer(self.container_name, None, None, None) self.directory = None
def test_get_container(self): rootfs = self.makeDir() container = LXCContainer(self.unit_name, None, None, None) mock_deploy = self.mocker.patch(self.unit_deploy) mock_deploy._get_master_template(ANY, ANY, ANY) self.mocker.result(container) mock_container = self.mocker.patch(container) mock_container.clone(ANY) self.mocker.result(container) self.mocker.replay() container, rootfs = yield self.unit_deploy._get_container( "0", "127.0.0.1:2181", None, "dsa...") output = self.output.getvalue() self.assertIn("Container created for %s" % self.unit_deploy.unit_name, output)
def test_container_clone(self): self.addCleanup(self.clean_container, DEFAULT_CONTAINER) self.addCleanup(self.clean_container, DEFAULT_CONTAINER + "_child") master_container = LXCContainer(DEFAULT_CONTAINER, origin="ppa", public_key="dsa...") # verify that we cannot clone an unconstructed container failure = master_container.clone("test_lxc_fail") yield self.assertFailure(failure, LXCError) yield master_container.create() # Clone a child container from the template child_name = DEFAULT_CONTAINER + "_child" c = yield master_container.clone(child_name) self.assertEqual(c.container_name, child_name) running = yield c.is_running() self.assertFalse(running) yield c.run() running = yield c.is_running() self.assertTrue(running) output = _lxc_ls() self.assertIn(DEFAULT_CONTAINER, output) self.verify_container(c, "dsa...", "ppa") # verify that we are in containers containers = yield get_containers(None) self.assertEqual(containers[child_name], True) # tear it down yield c.destroy() running = yield c.is_running() self.assertFalse(running) containers = yield get_containers(None) self.assertNotIn(child_name, containers) # and its gone output = _lxc_ls() self.assertNotIn(child_name, output) yield master_container.destroy()
def _get_master_template(self, machine_id, zookeeper_hosts, public_key): container_template_name = "%s-%s-template" % (self._unit_namespace, machine_id) master_template = LXCContainer(container_template_name, origin=self._juju_origin, public_key=public_key) # Debug log for the customize script, customize is only run on master. customize_log_path = os.path.join(self.juju_home, "units", "master-customize.log") master_template.customize_log = customize_log_path if not master_template.is_constructed(): log.debug("Creating master container...") yield master_template.create() log.debug("Created master container %s" % container_template_name) # it wasn't constructed and we couldn't construct it if not master_template.is_constructed(): raise LXCError("Unable to create master container") returnValue(master_template)
def test_start(self): container = LXCContainer(self.unit_name, None, None, None) rootfs = self.makeDir() env = dict(os.environ) env["JUJU_PUBLIC_KEY"] = "dsa ..." self.change_environment(**env) mock_deploy = self.mocker.patch(self.unit_deploy) # this minimally validates that we are also called with the # expect public key mock_deploy._get_container(ANY, ANY, ANY, env["JUJU_PUBLIC_KEY"]) self.mocker.result((container, rootfs)) mock_container = self.mocker.patch(container) mock_container.run() self.mocker.replay() self.unit_deploy.directory = rootfs os.makedirs(os.path.join(rootfs, "etc", "init")) yield self.unit_deploy.start("0", "127.0.1.1:2181", self.bundle) # Verify the upstart job upstart_agent_name = "%s-unit-agent.conf" % (self.unit_name.replace( "/", "-")) content = open(os.path.join(rootfs, "etc", "init", upstart_agent_name)).read() job = self.get_normalized(content) self.assertIn('JUJU_ZOOKEEPER="127.0.1.1:2181"', job) self.assertIn('JUJU_MACHINE_ID="0"', job) self.assertIn('JUJU_UNIT_NAME="riak/0"', job) # Verify the symlink exists self.assertTrue( os.path.lexists( os.path.join(self.unit_deploy.juju_home, "units", self.unit_deploy.unit_path_name, "unit.log"))) # Verify the charm is on disk. self.assertTrue( os.path.exists( os.path.join(self.unit_deploy.directory, "var", "lib", "juju", "units", self.unit_deploy.unit_path_name, "charm", "metadata.yaml"))) # Verify the directory structure in the unit. self.assertTrue( os.path.exists( os.path.join(self.unit_deploy.directory, "var", "lib", "juju", "state"))) self.assertTrue( os.path.exists( os.path.join(self.unit_deploy.directory, "var", "log", "juju"))) # Verify log output output = self.output.getvalue() self.assertIn("Charm extracted into container", output) self.assertIn("Started container for %s" % self.unit_deploy.unit_name, output)
class UnitContainerDeployment(UnitMachineDeployment): """Deploy a service unit in a container. Units deployed in a container have strong isolation between others units deployed in a container on the same machine. From the perspective of the service unit, the container deployment should be indistinguishable from a machine deployment. Note, strong isolation properties are still fairly trivial to escape for a user with a root account within the container. This is an ongoing development topic for LXC. """ def __init__(self, unit_name, juju_home): super(UnitContainerDeployment, self).__init__(unit_name, juju_home) self._unit_namespace = os.environ.get("JUJU_UNIT_NS") self._juju_origin = os.environ.get("JUJU_ORIGIN") assert self._unit_namespace is not None, "Required unit ns not found" self.pid_file = None self.container = LXCContainer(self.container_name, None, None, None) @property def container_name(self): """Get a qualfied name for the container. The units directory for the machine points to a path like:: /var/lib/juju/units In the case of the local provider this directory is qualified to allow for multiple users with multiple environments:: /var/lib/juju/username-envname This value is passed to the agent via the JUJU_HOME environment variable. This function extracts the name qualifier for the container from the JUJU_HOME value. """ return "%s-%s" % (self._unit_namespace, self.unit_name.replace("/", "-")) def setup_directories(self): # Create state directories for unit in the container # Move to juju-create script units_dir = os.path.join( self.directory, "var", "lib", "juju", "units") if not os.path.exists(units_dir): os.makedirs(units_dir) state_dir = os.path.join( self.directory, "var", "lib", "juju", "state") if not os.path.exists(state_dir): os.makedirs(state_dir) log_dir = os.path.join( self.directory, "var", "log", "juju") if not os.path.exists(log_dir): os.makedirs(log_dir) unit_dir = os.path.join(units_dir, self.unit_path_name) if not os.path.exists(unit_dir): os.mkdir(unit_dir) host_unit_dir = os.path.join( self.juju_home, "units", self.unit_path_name) if not os.path.exists(host_unit_dir): os.makedirs(host_unit_dir) @inlineCallbacks def _get_master_template(self, machine_id, zookeeper_hosts, public_key): container_template_name = "%s-%s-template" % ( self._unit_namespace, machine_id) master_template = LXCContainer( container_template_name, origin=self._juju_origin, public_key=public_key) # Debug log for the customize script, customize is only run on master. customize_log_path = os.path.join( self.juju_home, "units", "master-customize.log") master_template.customize_log = customize_log_path if not master_template.is_constructed(): log.debug("Creating master container...") yield master_template.create() log.debug("Created master container %s" % container_template_name) # it wasn't constructed and we couldn't construct it if not master_template.is_constructed(): raise LXCError("Unable to create master container") returnValue(master_template) @inlineCallbacks def _get_container(self, machine_id, zookeeper_hosts, bundle, public_key): master_template = yield self._get_master_template( machine_id, zookeeper_hosts, public_key) log.info( "Creating container %s...", os.path.basename(self.directory)) container = yield master_template.clone(self.container_name) directory = container.rootfs log.info("Container created for %s" % self.unit_name) returnValue((container, directory)) @inlineCallbacks def start(self, machine_id, zookeeper_hosts, bundle): """Start the unit. Creates and starts an lxc container for the unit. """ # remove any quoting around the key public_key = os.environ.get("JUJU_PUBLIC_KEY", "") public_key = public_key.strip("'\"") # Build a template container that can be cloned in deploy # we leave the loosely initialized self.container in place for # the class as thats all we need for methods other than start. self.container, self.directory = yield self._get_container(machine_id, zookeeper_hosts, bundle, public_key) # Create state directories for unit in the container self.setup_directories() # Extract the charm bundle charm_path = os.path.join( self.directory, "var", "lib", "juju", "units", self.unit_path_name, "charm") bundle.extract_to(charm_path) log.debug("Charm extracted into container") # Write upstart file for the agent into the container upstart_path = os.path.join( self.directory, "etc", "init", "%s-unit-agent.conf" % self.unit_path_name) with open(upstart_path, "w") as fh: fh.write(self.get_upstart_unit_job(machine_id, zookeeper_hosts)) # Create a symlink on the host for easier access to the unit log file unit_log_path_host = os.path.join( self.juju_home, "units", self.unit_path_name, "unit.log") if not os.path.lexists(unit_log_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s.log" % self.unit_path_name), unit_log_path_host) # Debug log for the container container_log_path = os.path.join( self.juju_home, "units", self.unit_path_name, "container.log") self.container.debug_log = container_log_path log.debug("Starting container...") yield self.container.run() log.info("Started container for %s" % self.unit_name) @inlineCallbacks def destroy(self): """Destroy the unit container. """ log.debug("Destroying container...") yield self.container.destroy() log.info("Destroyed container for %s" % self.unit_name) @inlineCallbacks def is_running(self): """Is the unit container running. """ # TODO: container running may not imply agent running. the # pid file has the pid from the container, we need a container # pid -> host pid mapping to query status from the machine agent. # alternatively querying zookeeper for the unit agent presence # node. if not self.container: returnValue(False) container_map = yield get_containers( prefix=self.container.container_name) returnValue(container_map.get(self.container.container_name, False)) def get_upstart_unit_job(self, machine_id, zookeeper_hosts): """Return a string containing the upstart job to start the unit agent. """ environ = self.get_environment(machine_id, zookeeper_hosts) # Keep qualified locations within the container for colo support environ["JUJU_HOME"] = "/var/lib/juju" environ["UNIT_PATH_NAME"] = self.unit_path_name return container_upstart_job_template % environ
def test_lxc_container(self): self.addCleanup(self.clean_container, DEFAULT_CONTAINER) customize_log = self.makeFile() c = LXCContainer( DEFAULT_CONTAINER, "dsa...", "ppa", customize_log=customize_log) running = yield c.is_running() self.assertFalse(running) self.assertFalse(c.is_constructed()) # verify we can't run a non-constructed container failure = c.run() yield self.assertFailure(failure, LXCError) yield c.create() self.assertFalse(running) self.assertTrue(c.is_constructed()) yield c.run() running = yield c.is_running() self.assertTrue(running) self.assertTrue(c.is_constructed()) output = _lxc_ls() self.assertIn(DEFAULT_CONTAINER, output) # verify we have a path into the container self.assertTrue(os.path.exists(c.rootfs)) self.assertTrue(c.is_constructed()) self.verify_container(c, "dsa...", "ppa") # verify that we are in containers containers = yield get_containers(None) self.assertEqual(containers[DEFAULT_CONTAINER], True) # tear it down yield c.destroy() running = yield c.is_running() self.assertFalse(running) containers = yield get_containers(None) self.assertNotIn(DEFAULT_CONTAINER, containers) # Verify the customize log file. self.assertTrue(os.path.exists(customize_log)) # and its gone output = _lxc_ls() self.assertNotIn(DEFAULT_CONTAINER, output)
class UnitContainerDeployment(UnitMachineDeployment): """Deploy a service unit in a container. Units deployed in a container have strong isolation between others units deployed in a container on the same machine. From the perspective of the service unit, the container deployment should be indistinguishable from a machine deployment. Note, strong isolation properties are still fairly trivial to escape for a user with a root account within the container. This is an ongoing development topic for LXC. """ def __init__(self, unit_name, juju_home): super(UnitContainerDeployment, self).__init__(unit_name, juju_home) self._unit_namespace = os.environ.get("JUJU_UNIT_NS") self._juju_origin = os.environ.get("JUJU_ORIGIN") assert self._unit_namespace is not None, "Required unit ns not found" self.pid_file = None self.container = LXCContainer(self.container_name, None, None, None) @property def container_name(self): """Get a qualfied name for the container. The units directory for the machine points to a path like:: /var/lib/juju/units In the case of the local provider this directory is qualified to allow for multiple users with multiple environments:: /var/lib/juju/username-envname This value is passed to the agent via the JUJU_HOME environment variable. This function extracts the name qualifier for the container from the JUJU_HOME value. """ return "%s-%s" % (self._unit_namespace, self.unit_name.replace( "/", "-")) def setup_directories(self): # Create state directories for unit in the container # Move to juju-create script units_dir = os.path.join(self.directory, "var", "lib", "juju", "units") if not os.path.exists(units_dir): os.makedirs(units_dir) state_dir = os.path.join(self.directory, "var", "lib", "juju", "state") if not os.path.exists(state_dir): os.makedirs(state_dir) log_dir = os.path.join(self.directory, "var", "log", "juju") if not os.path.exists(log_dir): os.makedirs(log_dir) unit_dir = os.path.join(units_dir, self.unit_path_name) if not os.path.exists(unit_dir): os.mkdir(unit_dir) host_unit_dir = os.path.join(self.juju_home, "units", self.unit_path_name) if not os.path.exists(host_unit_dir): os.makedirs(host_unit_dir) @inlineCallbacks def _get_master_template(self, machine_id, zookeeper_hosts, public_key): container_template_name = "%s-%s-template" % (self._unit_namespace, machine_id) master_template = LXCContainer(container_template_name, origin=self._juju_origin, public_key=public_key) # Debug log for the customize script, customize is only run on master. customize_log_path = os.path.join(self.juju_home, "units", "master-customize.log") master_template.customize_log = customize_log_path if not master_template.is_constructed(): log.debug("Creating master container...") yield master_template.create() log.debug("Created master container %s" % container_template_name) # it wasn't constructed and we couldn't construct it if not master_template.is_constructed(): raise LXCError("Unable to create master container") returnValue(master_template) @inlineCallbacks def _get_container(self, machine_id, zookeeper_hosts, bundle, public_key): master_template = yield self._get_master_template( machine_id, zookeeper_hosts, public_key) log.info("Creating container %s...", os.path.basename(self.directory)) container = yield master_template.clone(self.container_name) directory = container.rootfs log.info("Container created for %s" % self.unit_name) returnValue((container, directory)) @inlineCallbacks def start(self, machine_id, zookeeper_hosts, bundle): """Start the unit. Creates and starts an lxc container for the unit. """ # remove any quoting around the key public_key = os.environ.get("JUJU_PUBLIC_KEY", "") public_key = public_key.strip("'\"") # Build a template container that can be cloned in deploy # we leave the loosely initialized self.container in place for # the class as thats all we need for methods other than start. self.container, self.directory = yield self._get_container( machine_id, zookeeper_hosts, bundle, public_key) # Create state directories for unit in the container self.setup_directories() # Extract the charm bundle charm_path = os.path.join(self.directory, "var", "lib", "juju", "units", self.unit_path_name, "charm") bundle.extract_to(charm_path) log.debug("Charm extracted into container") # Write upstart file for the agent into the container upstart_path = os.path.join(self.directory, "etc", "init", "%s-unit-agent.conf" % self.unit_path_name) with open(upstart_path, "w") as fh: fh.write(self.get_upstart_unit_job(machine_id, zookeeper_hosts)) # Create a symlink on the host for easier access to the unit log file unit_log_path_host = os.path.join(self.juju_home, "units", self.unit_path_name, "unit.log") if not os.path.lexists(unit_log_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s.log" % self.unit_path_name), unit_log_path_host) # Debug log for the container container_log_path = os.path.join(self.juju_home, "units", self.unit_path_name, "container.log") self.container.debug_log = container_log_path log.debug("Starting container...") yield self.container.run() log.info("Started container for %s" % self.unit_name) @inlineCallbacks def destroy(self): """Destroy the unit container. """ log.debug("Destroying container...") yield self.container.destroy() log.info("Destroyed container for %s" % self.unit_name) @inlineCallbacks def is_running(self): """Is the unit container running. """ # TODO: container running may not imply agent running. the # pid file has the pid from the container, we need a container # pid -> host pid mapping to query status from the machine agent. # alternatively querying zookeeper for the unit agent presence # node. if not self.container: returnValue(False) container_map = yield get_containers( prefix=self.container.container_name) returnValue(container_map.get(self.container.container_name, False)) def get_upstart_unit_job(self, machine_id, zookeeper_hosts): """Return a string containing the upstart job to start the unit agent. """ environ = self.get_environment(machine_id, zookeeper_hosts) # Keep qualified locations within the container for colo support environ["JUJU_HOME"] = "/var/lib/juju" environ["UNIT_PATH_NAME"] = self.unit_path_name return container_upstart_job_template % environ
class UnitContainerDeployment(object): """Deploy a service unit in a container. Units deployed in a container have strong isolation between others units deployed in a container on the same machine. From the perspective of the service unit, the container deployment should be indistinguishable from a machine deployment. Note, strong isolation properties are still fairly trivial to escape for a user with a root account within the container. This is an ongoing development topic for LXC. """ def __init__(self, unit_name, juju_home): self.unit_name = unit_name self.juju_home = juju_home self.unit_path_name = unit_name.replace("/", "-") self._juju_origin = os.environ.get("JUJU_ORIGIN") self._juju_series = os.environ.get("JUJU_SERIES") assert self._juju_series is not None, "Required juju series not found" self._unit_namespace = os.environ.get("JUJU_UNIT_NS") assert self._unit_namespace is not None, "Required unit ns not found" self.container_name = "%s-%s" % ( self._unit_namespace, self.unit_path_name) self.container = LXCContainer(self.container_name, None, None, None) self.directory = None def setup_directories(self): # Create state directories for unit in the container # Move to juju-create script base = self.directory dirs = ((base, "var", "lib", "juju", "units", self.unit_path_name), (base, "var", "lib", "juju", "state"), (base, "var", "log", "juju"), (self.juju_home, "units", self.unit_path_name)) for parts in dirs: dir_ = os.path.join(*parts) if not os.path.exists(dir_): os.makedirs(dir_) @inlineCallbacks def _get_master_template(self, machine_id, public_key): container_template_name = "%s-%s-template" % ( self._unit_namespace, machine_id) master_template = LXCContainer( container_template_name, origin=self._juju_origin, public_key=public_key, series=self._juju_series) # Debug log for the customize script, customize is only run on master. customize_log_path = os.path.join( self.juju_home, "units", "master-customize.log") master_template.customize_log = customize_log_path if not master_template.is_constructed(): log.debug("Creating master container...") yield master_template.create() log.debug("Created master container %s", container_template_name) # it wasn't constructed and we couldn't construct it if not master_template.is_constructed(): raise LXCError("Unable to create master container") returnValue(master_template) @inlineCallbacks def _get_container(self, machine_id, bundle, public_key): master_template = yield self._get_master_template( machine_id, public_key) log.info( "Creating container %s...", self.unit_path_name) container = yield master_template.clone(self.container_name) directory = container.rootfs log.info("Container created for %s", self.unit_name) returnValue((container, directory)) @inlineCallbacks def start(self, machine_id, zookeeper_hosts, bundle): """Start the unit. Creates and starts an lxc container for the unit. """ # remove any quoting around the key public_key = os.environ.get("JUJU_PUBLIC_KEY", "") public_key = public_key.strip("'\"") # Build a template container that can be cloned in deploy # we leave the loosely initialized self.container in place for # the class as thats all we need for methods other than start. self.container, self.directory = yield self._get_container( machine_id, bundle, public_key) # Create state directories for unit in the container self.setup_directories() # Extract the charm bundle charm_path = os.path.join( self.directory, "var", "lib", "juju", "units", self.unit_path_name, "charm") bundle.extract_to(charm_path) log.debug("Charm extracted into container") # Write upstart file for the agent into the container service_name = "juju-%s" % self.unit_path_name init_dir = os.path.join(self.directory, "etc", "init") service = UpstartService(service_name, init_dir=init_dir) service.set_description( "Juju unit agent for %s" % self.unit_name) service.set_environ(_get_environment( self.unit_name, "/var/lib/juju", machine_id, zookeeper_hosts)) service.set_output_path( "/var/log/juju/unit-%s-output.log" % self.unit_path_name) service.set_command(" ".join(( "/usr/bin/python", "-m", "juju.agents.unit", "--nodaemon", "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name, "--session-file", "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) yield service.install() # Create symlinks on the host for easier access to the unit log files unit_log_path_host = os.path.join( self.juju_home, "units", self.unit_path_name, "unit.log") if not os.path.lexists(unit_log_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s.log" % self.unit_path_name), unit_log_path_host) unit_output_path_host = os.path.join( self.juju_home, "units", self.unit_path_name, "output.log") if not os.path.lexists(unit_output_path_host): os.symlink( os.path.join(self.directory, "var", "log", "juju", "unit-%s-output.log" % self.unit_path_name), unit_output_path_host) # Debug log for the container container_log_path = os.path.join( self.juju_home, "units", self.unit_path_name, "container.log") self.container.debug_log = container_log_path log.debug("Starting container...") yield self.container.run() log.info("Started container for %s", self.unit_name) @inlineCallbacks def destroy(self): """Destroy the unit container.""" log.debug("Destroying container...") yield self.container.destroy() log.info("Destroyed container for %s", self.unit_name) @inlineCallbacks def is_running(self): """Is the unit container running?""" # TODO: container running may not imply agent running. # query zookeeper for the unit agent presence node? if not self.container: returnValue(False) container_map = yield get_containers( prefix=self.container.container_name) returnValue(container_map.get(self.container.container_name, False))