Exemple #1
0
class Snapshot(model.Model):
    """A container snapshot."""
    name = model.Attribute()
    stateful = model.Attribute()

    container = model.Parent()

    @property
    def api(self):
        return self.client.api.containers[self.container.name].snapshots[
            self.name]

    @classmethod
    def get(cls, client, container, name):
        try:
            response = client.api.containers[
                container.name].snapshots[name].get()
        except exceptions.LXDAPIException as e:
            if e.response.status_code == 404:
                raise exceptions.NotFound()
            raise

        snapshot = cls(client,
                       container=container,
                       **response.json()['metadata'])
        # Snapshot names are namespaced in LXD, as
        # container-name/snapshot-name. We hide that implementation
        # detail.
        snapshot.name = snapshot.name.split('/')[-1]
        return snapshot

    @classmethod
    def all(cls, client, container):
        response = client.api.containers[container.name].snapshots.get()

        return [
            cls(client, name=snapshot.split('/')[-1], container=container)
            for snapshot in response.json()['metadata']
        ]

    @classmethod
    def create(cls, client, container, name, stateful=False, wait=False):
        response = client.api.containers[container.name].snapshots.post(
            json={
                'name': name,
                'stateful': stateful
            })

        snapshot = cls(client, container=container, name=name)
        if wait:
            Operation.wait_for_operation(client, response.json()['operation'])
        return snapshot

    def rename(self, new_name, wait=False):
        """Rename a snapshot."""
        response = self.api.post(json={'name': new_name})
        if wait:
            Operation.wait_for_operation(self.client,
                                         response.json()['operation'])
        self.name = new_name
Exemple #2
0
class Item(model.Model):
    """A fake model."""
    name = model.Attribute(readonly=True)
    age = model.Attribute(int)
    data = model.Attribute()

    @property
    def api(self):
        return self.client.api.items[self.name]
Exemple #3
0
class Profile(model.Model):
    """A LXD profile."""

    name = model.Attribute(readonly=True)
    description = model.Attribute()
    config = model.Attribute()
    devices = model.Attribute()

    @classmethod
    def get(cls, client, name):
        """Get a profile."""
        try:
            response = client.api.profiles[name].get()
        except exceptions.LXDAPIException as e:
            if e.response.status_code == 404:
                raise exceptions.NotFound()
            raise

        return cls(client, **response.json()['metadata'])

    @classmethod
    def all(cls, client):
        """Get all profiles."""
        response = client.api.profiles.get()

        profiles = []
        for url in response.json()['metadata']:
            name = url.split('/')[-1]
            profiles.append(cls(client, name=name))
        return profiles

    @classmethod
    def create(cls, client, name, config=None, devices=None):
        """Create a profile."""
        profile = {'name': name}
        if config is not None:
            profile['config'] = config
        if devices is not None:
            profile['devices'] = devices
        try:
            client.api.profiles.post(json=profile)
        except exceptions.LXDAPIException as e:
            raise exceptions.CreateFailed(e.response.json())

        return cls.get(client, name)

    @property
    def api(self):
        return self.client.api.profiles[self.name]

    def rename(self, new_name):
        """Rename the profile."""
        self.api.post(json={'name': new_name})

        return Profile.get(self.client, new_name)
Exemple #4
0
class Certificate(model.Model):
    """A LXD certificate."""

    certificate = model.Attribute()
    fingerprint = model.Attribute()
    type = model.Attribute()

    @classmethod
    def get(cls, client, fingerprint):
        """Get a certificate by fingerprint."""
        response = client.api.certificates[fingerprint].get()

        return cls(client, **response.json()['metadata'])

    @classmethod
    def all(cls, client):
        """Get all certificates."""
        response = client.api.certificates.get()

        certs = []
        for cert in response.json()['metadata']:
            fingerprint = cert.split('/')[-1]
            certs.append(cls(client, fingerprint=fingerprint))
        return certs

    @classmethod
    def create(cls, client, password, cert_data):
        """Create a new certificate."""
        cert = x509.load_pem_x509_certificate(cert_data, default_backend())
        base64_cert = cert.public_bytes(Encoding.PEM).decode('utf-8')
        # STRIP OUT CERT META "-----BEGIN CERTIFICATE-----"
        base64_cert = '\n'.join(base64_cert.split('\n')[1:-2])
        data = {
            'type': 'client',
            'certificate': base64_cert,
            'password': password,
        }
        client.api.certificates.post(json=data)

        # XXX: rockstar (08 Jun 2016) - Please see the open lxd bug here:
        # https://github.com/lxc/lxd/issues/2092
        fingerprint = binascii.hexlify(cert.fingerprint(
            hashes.SHA256())).decode('utf-8')
        return cls.get(client, fingerprint)

    @property
    def api(self):
        return self.client.api.certificates[self.fingerprint]
Exemple #5
0
class Network(model.Model):
    """A LXD network."""
    name = model.Attribute()
    type = model.Attribute()
    used_by = model.Attribute()

    @classmethod
    def get(cls, client, name):
        """Get a network by name."""
        response = client.api.networks[name].get()

        network = cls(client, **response.json()['metadata'])
        return network

    @classmethod
    def all(cls, client):
        """Get all networks."""
        response = client.api.networks.get()

        networks = []
        for url in response.json()['metadata']:
            name = url.split('/')[-1]
            networks.append(cls(client, name=name))
        return networks

    @property
    def api(self):
        return self.client.api.networks[self.name]

    def save(self, wait=False):
        """Save is not available for networks."""
        raise NotImplementedError('save is not implemented')

    def delete(self):
        """Delete is not available for networks."""
        raise NotImplementedError('delete is not implemented')
Exemple #6
0
class Container(model.Model):
    """An LXD Container.

    This class is not intended to be used directly, but rather to be used
    via `Client.containers.create`.
    """
    architecture = model.Attribute()
    config = model.Attribute()
    created_at = model.Attribute()
    devices = model.Attribute()
    ephemeral = model.Attribute()
    expanded_config = model.Attribute()
    expanded_devices = model.Attribute()
    name = model.Attribute(readonly=True)
    profiles = model.Attribute()
    status = model.Attribute(readonly=True)

    status_code = model.Attribute(readonly=True)
    stateful = model.Attribute(readonly=True)

    snapshots = model.Manager()
    files = model.Manager()

    @property
    def api(self):
        return self.client.api.containers[self.name]

    class FilesManager(object):
        """A pseudo-manager for namespacing file operations."""
        def __init__(self, client, container):
            self._client = client
            self._container = container

        def put(self, filepath, data):
            response = self._client.api.containers[
                self._container.name].files.post(params={'path': filepath},
                                                 data=data)
            return response.status_code == 200

        def get(self, filepath):
            try:
                response = self._client.api.containers[
                    self._container.name].files.get(params={'path': filepath})
            except exceptions.LXDAPIException as e:
                # LXD 2.0.3+ return 404, not 500,
                if e.response.status_code in (500, 404):
                    raise exceptions.NotFound()
                raise
            return response.content

    @classmethod
    def get(cls, client, name):
        """Get a container by name."""
        try:
            response = client.api.containers[name].get()
        except exceptions.LXDAPIException as e:
            if e.response.status_code == 404:
                raise exceptions.NotFound()
            raise

        container = cls(client, **response.json()['metadata'])
        return container

    @classmethod
    def all(cls, client):
        """Get all containers.

        Containers returned from this method will only have the name
        set, as that is the only property returned from LXD. If more
        information is needed, `Container.sync` is the method call
        that should be used.
        """
        response = client.api.containers.get()

        containers = []
        for url in response.json()['metadata']:
            name = url.split('/')[-1]
            containers.append(cls(client, name=name))
        return containers

    @classmethod
    def create(cls, client, config, wait=False):
        """Create a new container config."""
        try:
            response = client.api.containers.post(json=config)
        except exceptions.LXDAPIException as e:
            raise exceptions.CreateFailed(e.response)

        if wait:
            Operation.wait_for_operation(client, response.json()['operation'])
        return cls(client, name=config['name'])

    def __init__(self, *args, **kwargs):
        super(Container, self).__init__(*args, **kwargs)

        self.snapshots = managers.SnapshotManager(self.client, self)
        self.files = self.FilesManager(self.client, self)

    # XXX: rockstar (28 Mar 2016) - This method was named improperly
    # originally. It's being kept here for backwards compatibility.
    reload = deprecated(
        "Container.reload is deprecated. Please use Container.sync")(
            model.Model.sync)

    def rename(self, name, wait=False):
        """Rename a container."""
        response = self.api.post(json={'name': name})

        if wait:
            Operation.wait_for_operation(self.client,
                                         response.json()['operation'])
        self.name = name

    def _set_state(self, state, timeout=30, force=True, wait=False):
        response = self.api.state.put(json={
            'action': state,
            'timeout': timeout,
            'force': force
        })
        if wait:
            Operation.wait_for_operation(self.client,
                                         response.json()['operation'])
            self.sync()

    def state(self):
        response = self.api.state.get()
        state = ContainerState(**response.json()['metadata'])
        return state

    def start(self, timeout=30, force=True, wait=False):
        """Start the container."""
        return self._set_state('start',
                               timeout=timeout,
                               force=force,
                               wait=wait)

    def stop(self, timeout=30, force=True, wait=False):
        """Stop the container."""
        return self._set_state('stop', timeout=timeout, force=force, wait=wait)

    def restart(self, timeout=30, force=True, wait=False):
        """Restart the container."""
        return self._set_state('restart',
                               timeout=timeout,
                               force=force,
                               wait=wait)

    def freeze(self, timeout=30, force=True, wait=False):
        """Freeze the container."""
        return self._set_state('freeze',
                               timeout=timeout,
                               force=force,
                               wait=wait)

    def unfreeze(self, timeout=30, force=True, wait=False):
        """Unfreeze the container."""
        return self._set_state('unfreeze',
                               timeout=timeout,
                               force=force,
                               wait=wait)

    @deprecated(
        'Container.snapshot is deprecated. Please use Container.snapshots.create'
    )  # NOQA
    def snapshot(self, name, stateful=False, wait=False):  # pragma: no cover
        """Take a snapshot of the container."""
        self.snapshots.create(name, stateful, wait)

    @deprecated(
        'Container.list_snapshots is deprecated. Please use Container.snapshots.all'
    )  # NOQA
    def list_snapshots(self):  # pragma: no cover
        """List all container snapshots."""
        return [s.name for s in self.snapshots.all()]

    @deprecated(
        'Container.rename_snapshot is deprecated. Please use Snapshot.rename'
    )  # NOQA
    def rename_snapshot(self, old, new, wait=False):  # pragma: no cover
        """Rename a snapshot."""
        snapshot = self.snapshots.get(old)
        snapshot.rename(new, wait=wait)

    @deprecated(
        'Container.delete_snapshot is deprecated. Please use Snapshot.delete'
    )  # NOQA
    def delete_snapshot(self, name, wait=False):  # pragma: no cover
        """Delete a snapshot."""
        snapshot = self.snapshots.get(name)
        snapshot.delete(wait=wait)

    @deprecated(
        'Container.get_file is deprecated. Please use Container.files.get'
    )  # NOQA
    def get_file(self, filepath):  # pragma: no cover
        """Get a file from the container."""
        return self.files.get(filepath)

    @deprecated(
        'Container.put_file is deprecated. Please use Container.files.put'
    )  # NOQA
    def put_file(self, filepath, data):  # pragma: no cover
        """Put a file on the container."""
        return self.files.put(filepath, data)

    def execute(self, commands, environment={}):
        """Execute a command on the container."""
        if isinstance(commands, six.string_types):
            raise TypeError("First argument must be a list.")
        response = self.api['exec'].post(
            json={
                'command': commands,
                'environment': environment,
                'wait-for-websocket': True,
                'interactive': False,
            })

        fds = response.json()['metadata']['metadata']['fds']
        operation_id = response.json()['operation'].split('/')[-1]
        parsed = parse.urlparse(
            self.client.api.operations[operation_id].websocket._api_endpoint)

        manager = WebSocketManager()

        stdin = _StdinWebsocket(manager, self.client.websocket_url)
        stdin.resource = '{}?secret={}'.format(parsed.path, fds['0'])
        stdin.connect()
        stdout = _CommandWebsocketClient(manager, self.client.websocket_url)
        stdout.resource = '{}?secret={}'.format(parsed.path, fds['1'])
        stdout.connect()
        stderr = _CommandWebsocketClient(manager, self.client.websocket_url)
        stderr.resource = '{}?secret={}'.format(parsed.path, fds['2'])
        stderr.connect()

        manager.start()

        while True:  # pragma: no cover
            for websocket in manager.websockets.values():
                if not websocket.terminated:
                    break
            else:
                break
            time.sleep(1)

        return stdout.data, stderr.data

    def migrate(self, new_client, wait=False):
        """Migrate a container.

        Destination host information is contained in the client
        connection passed in.

        If the container is running, it either must be shut down
        first or criu must be installed on the source and destination
        machines.
        """
        self.sync()  # Make sure the object isn't stale
        response = self.api.post(json={'migration': True})
        operation = self.client.operations.get(response.json()['operation'])
        operation_url = self.client.api.operations[operation.id]._api_endpoint
        secrets = response.json()['metadata']['metadata']
        cert = self.client.host_info['environment']['certificate']

        config = {
            'name': self.name,
            'architecture': self.architecture,
            'config': self.config,
            'devices': self.devices,
            'epehemeral': self.ephemeral,
            'default': self.profiles,
            'source': {
                'type': 'migration',
                'operation': operation_url,
                'mode': 'pull',
                'certificate': cert,
                'secrets': secrets,
            }
        }
        return new_client.containers.create(config, wait=wait)
Exemple #7
0
class Image(model.Model):
    """A LXD Image."""
    aliases = model.Attribute()
    auto_update = model.Attribute()
    architecture = model.Attribute()
    cached = model.Attribute()
    created_at = model.Attribute()
    expires_at = model.Attribute()
    filename = model.Attribute()
    fingerprint = model.Attribute()
    last_used_at = model.Attribute()
    properties = model.Attribute()
    public = model.Attribute()
    size = model.Attribute()
    uploaded_at = model.Attribute()

    @property
    def api(self):
        return self.client.api.images[self.fingerprint]

    @classmethod
    def get(cls, client, fingerprint):
        """Get an image."""
        try:
            response = client.api.images[fingerprint].get()
        except exceptions.LXDAPIException as e:
            if e.response.status_code == 404:
                raise exceptions.NotFound()
            raise

        image = cls(client, **response.json()['metadata'])
        return image

    @classmethod
    def all(cls, client):
        """Get all images."""
        response = client.api.images.get()

        images = []
        for url in response.json()['metadata']:
            fingerprint = url.split('/')[-1]
            images.append(cls(client, fingerprint=fingerprint))
        return images

    @classmethod
    def create(cls, client, image_data, public=False, wait=False):
        """Create an image."""
        fingerprint = hashlib.sha256(image_data).hexdigest()

        headers = {}
        if public:
            headers['X-LXD-Public'] = '1'
        try:
            response = client.api.images.post(data=image_data, headers=headers)
        except exceptions.LXDAPIException as e:
            raise exceptions.CreateFailed(e.response.json())

        if wait:
            Operation.wait_for_operation(client, response.json()['operation'])
        return cls(client, fingerprint=fingerprint)

    def export(self):
        """Export the image."""
        try:
            response = self.api.export.get()
        except exceptions.LXDAPIException as e:
            if e.response.status_code == 404:
                raise exceptions.NotFound()
            raise

        return response.content