This file is indexed.

/usr/share/pyshared/juju/machine/unit.py is in juju-0.7 0.7-0ubuntu2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
import os
import shutil
import logging

import juju

from twisted.internet.defer import inlineCallbacks, returnValue

from juju.charm.bundle import CharmBundle
from juju.errors import ServiceError
from juju.lib.lxc import LXCContainer, get_containers
from juju.lib.twistutils import get_module_directory
from juju.lib.upstart import UpstartService
from juju.providers.common.cloudinit import CloudInit

from .errors import UnitDeploymentError

log = logging.getLogger("unit.deploy")


def get_deploy_factory(provider_type):
    if provider_type == "local":
        return UnitContainerDeployment
    elif provider_type == "subordinate":
        return SubordinateContainerDeployment

    return UnitMachineDeployment


def _get_environment(unit_name,
                     juju_home, machine_id, zookeeper_hosts, env_id):
    """
    Return environment dictionary for unit.
    """
    environ = dict()
    environ["JUJU_MACHINE_ID"] = str(machine_id)
    environ["JUJU_UNIT_NAME"] = unit_name
    environ["JUJU_HOME"] = juju_home
    environ["JUJU_ZOOKEEPER"] = zookeeper_hosts
    environ["JUJU_ENV_UUID"] = env_id
    environ["PYTHONPATH"] = ":".join(
        filter(None, [
            os.path.dirname(get_module_directory(juju)),
            os.environ.get("PYTHONPATH")]))
    return environ


class UnitMachineDeployment(object):
    """ Deploy a unit directly onto a machine.

    A service unit deployed directly to a machine has full access
    to the machine resources.

    Units deployed in such a manner have no isolation from other units
    on the machine, and may leave artifacts on the machine even upon
    service destruction.
    """

    unit_agent_module = "juju.agents.unit"

    def __init__(self, unit_name, juju_home):
        assert ".." not in unit_name, "Invalid Unit Name"
        self.unit_name = unit_name
        self.juju_home = juju_home
        self.unit_path_name = unit_name.replace("/", "-")
        self.directory = os.path.join(
            self.juju_home, "units", self.unit_path_name)
        self.service = UpstartService(
            # NOTE: we need use_sudo to work correctly during tests that
            # launch actual processes (rather than just mocking/trusting).
            "juju-%s" % self.unit_path_name, use_sudo=True)

    @inlineCallbacks
    def start(self, env_id, machine_id, zookeeper_hosts, bundle):
        """Start a service unit agent."""
        self.unpack_charm(bundle)
        self.service.set_description(
            "Juju unit agent for %s" % self.unit_name)
        self.service.set_environ(_get_environment(
            self.unit_name, self.juju_home, machine_id,
            zookeeper_hosts, env_id))
        self.service.set_command(" ".join((
            "/usr/bin/python", "-m", self.unit_agent_module,
            "--nodaemon",
            "--logfile", os.path.join(self.directory, "charm.log"),
            "--session-file",
            "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name)))
        try:
            yield self.service.start()
        except ServiceError as e:
            raise UnitDeploymentError(str(e))

    @inlineCallbacks
    def destroy(self):
        """Forcibly terminate a service unit agent, and clean disk state.

        This will destroy/unmount any state on disk.
        """
        yield self.service.destroy()
        if os.path.exists(self.directory):
            shutil.rmtree(self.directory)

    def get_pid(self):
        """Get the service unit's process id."""
        return self.service.get_pid()

    def is_running(self):
        """Is the service unit running."""
        return self.service.is_running()

    def unpack_charm(self, charm):
        """Unpack a charm to the service units directory."""
        if not isinstance(charm, CharmBundle):
            raise UnitDeploymentError(
                "Invalid charm for deployment: %s" % charm.path)

        charm.extract_to(os.path.join(self.directory, "charm"))


class SubordinateContainerDeployment(UnitMachineDeployment):
    """Deploy a subordinate unit.

    Assumes the basic runtime has been built for/by the principal
    service or its machine agent.
    """
    def __init__(self, unit_name, juju_home):
        assert ".." not in unit_name, "Invalid Unit Name"
        self.unit_name = unit_name
        self.juju_home = juju_home
        self.unit_path_name = unit_name.replace("/", "-")
        self.directory = os.path.join(
            self.juju_home, "units", self.unit_path_name)
        self.service = UpstartService(
            # NOTE: we need use_sudo to work correctly during tests that
            # launch actual processes (rather than just mocking/trusting).
            "juju-%s" % self.unit_path_name, use_sudo=True)


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
        self._container_juju_home = '/var/lib/juju'

    def setup_directories(self):
        # Create state directories for unit in the container
        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_)

    def _get_cloud_init(self, zookeepers):
        cloud_init = CloudInit()

        # remove any quoting around the key
        authorized_keys = os.environ.get("JUJU_PUBLIC_KEY", "")
        authorized_keys = authorized_keys.strip("'\"")

        cloud_init.add_ssh_key(authorized_keys)

        zks = []
        for zk in zookeepers.split(','):
            if ':' in zk:
                (zk, port) = zk.split(':')
            else:
                port = 2181
            zks.append((zk, port))

        cloud_init.set_zookeeper_hosts(zks)
        # XXX Very hard to access the provider's notion of network
        # or even env configs, so just assume the first ZK is running
        # the apt-cacher-ng since this is meant for local provider.
        cloud_init.set_apt_proxy('http://%s:3142' % zks[0][0])

        if self._juju_origin:
            if self._juju_origin.startswith("lp:"):
                cloud_init.set_juju_source(branch=self._juju_origin)
            elif self._juju_origin == "ppa":
                cloud_init.set_juju_source(ppa=True)
            elif self._juju_origin == "proposed":
                cloud_init.set_juju_source(proposed=True)
            else:
                # Ignore other values, just use the distro for sanity
                cloud_init.set_juju_source(distro=True)
        cloud_init.set_unit_name(self.unit_name)
        cloud_init.set_juju_home(self._container_juju_home)
        return cloud_init

    @inlineCallbacks
    def _get_container(self, machine_id, cloud_init):
        container = LXCContainer(
            self.container_name,
            cloud_init=cloud_init,
            series=self._juju_series)

        if not container.is_constructed():
            log.info(
                "Creating container %s...", self.unit_path_name)
            yield container.create()
            log.info("Created container %s", self.container_name)

        directory = container.rootfs
        returnValue((container, directory))

    @inlineCallbacks
    def start(self, env_id, machine_id, zookeeper_hosts, bundle):
        """Start the unit.

        Creates and starts an lxc container for the unit.
        """

        # 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.
        cloud_init = self._get_cloud_init(zookeeper_hosts)

        self.container, self.directory = yield self._get_container(
            machine_id, cloud_init)

        # 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")

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