<commit_before># Copyright Hybrid Logic Ltd.  See LICENSE file for details.
# -*- test-case-name: flocker.volume.test.test_service -*-

"""
Volume manager service, the main entry point that manages volumes.
"""

from __future__ import absolute_import

import os
import sys
import json
import stat
from uuid import UUID, uuid4

from zope.interface import Interface, implementer

from characteristic import attributes

from twisted.python.filepath import FilePath
from twisted.application.service import Service
from twisted.internet.endpoints import ProcessEndpoint, connectProtocol
from twisted.internet import reactor
from twisted.internet.defer import fail
from twisted.internet.task import LoopingCall

# We might want to make these utilities shared, rather than in zfs
# module... but in this case the usage is temporary and should go away as
# part of https://github.com/ClusterHQ/flocker/issues/64
from .filesystems.zfs import _AccumulatingProtocol, CommandFailed, StoragePool
from ..common.script import ICommandLineScript

DEFAULT_CONFIG_PATH = FilePath(b"/etc/flocker/volume.json")
FLOCKER_MOUNTPOINT = FilePath(b"/flocker")
FLOCKER_POOL = b"flocker"

WAIT_FOR_VOLUME_INTERVAL = 0.1


class CreateConfigurationError(Exception):
    """Create the configuration file failed."""


class VolumeService(Service):
    """
    Main service for volume management.

    :ivar unicode uuid: A unique identifier for this particular node's
        volume manager. Only available once the service has started.
    """

    def __init__(self, config_path, pool, reactor):
        """
        :param FilePath config_path: Path to the volume manager config file.
        :param pool: An object that is both a
            ``flocker.volume.filesystems.interface.IStoragePool`` provider
            and a ``twisted.application.service.IService`` provider.
        :param reactor: A ``twisted.internet.interface.IReactorTime`` provider.
        """
        self._config_path = config_path
        self.pool = pool
        self._reactor = reactor

    def startService(self):
        Service.startService(self)
        parent = self._config_path.parent()
        try:
            if not parent.exists():
                parent.makedirs()
            if not self._config_path.exists():
                uuid = unicode(uuid4())
                self._config_path.setContent(json.dumps({u"uuid": uuid,
                                                         u"version": 1}))
        except OSError as e:
            raise CreateConfigurationError(e.args[1])
        config = json.loads(self._config_path.getContent())
        self.uuid = config[u"uuid"]
        self.pool.startService()

    def create(self, name):
        """Create a new volume.

        :param unicode name: The name of the volume.

        :return: A ``Deferred`` that fires with a :class:`Volume`.
        """
        volume = Volume(uuid=self.uuid, name=name, service=self)
        d = self.pool.create(volume)

        def created(filesystem):
            filesystem.get_path().chmod(
                # 0o777 the long way:
                stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
            return volume
        d.addCallback(created)
        return d

    def get(self, name):
        """
        Return a locally-owned ``Volume`` with the given name.

        Whether or not this volume actually exists is not checked in any
        way.

        :param unicode name: The name of the volume.

        :param uuid: Either ``None``, in which case the local UUID will be
            used, or a UUID to use for the volume.

        :return: A ``Volume``.
        """
        return Volume(uuid=self.uuid, name=name, service=self)

    def wait_for_volume(self, name):
        """
        Wait for a volume by the given name, owned by thus service, to exist.

        Polls the storage pool for the specified volume to appear.

        :param unicode name: The name of the volume.

        :return: A ``Deferred`` that fires with a :class:`Volume`.
        """
        volume = Volume(uuid=self.uuid, name=name, service=self)

        def check_for_volume(volumes):
            if volume in volumes:
                call.stop()

        def loop():
            d = self.enumerate()
            d.addCallback(check_for_volume)
            return d

        call = LoopingCall(loop)
        call.clock = self._reactor
        d = call.start(WAIT_FOR_VOLUME_INTERVAL)
        d.addCallback(lambda _: volume)
        return d

    def enumerate(self):
        """Get a listing of all volumes managed by this service.

        :return: A ``Deferred`` that fires with an iterator of :class:`Volume`.
        """
        enumerating = self.pool.enumerate()

        def enumerated(filesystems):
            for filesystem in filesystems:
                # XXX It so happens that this works but it's kind of a
                # fragile way to recover the information:
                #    https://github.com/ClusterHQ/flocker/issues/78
                basename = filesystem.get_path().basename()
                try:
                    uuid, name = basename.split(b".", 1)
                    uuid = UUID(uuid)
                except ValueError:
                    # If we can't split on `.` and get two parts then it's not
                    # a filesystem Flocker is managing.  Likewise if we can't
                    # interpret the bit before the `.` as a UUID.  Perhaps a
                    # user created it, who knows.  Just ignore it.
                    continue

                # Probably shouldn't yield this volume if the uuid doesn't
                # match this service's uuid.
                yield Volume(
                    uuid=unicode(uuid),
                    name=name.decode('utf8'),
                    service=self)
        enumerating.addCallback(enumerated)
        return enumerating

    def push(self, volume, destination, config_path=DEFAULT_CONFIG_PATH):
        """
        Push the latest data in the volume to a remote destination.

        This is a blocking API for now.

        Only locally owned volumes (i.e. volumes whose ``uuid`` matches
        this service's) can be pushed.

        :param Volume volume: The volume to push.

        :param IRemoteVolumeManager destination: The remote volume manager
            to push to.

        :param FilePath config_path: Path to configuration file for the
            remote ``flocker-volume``.

        :raises ValueError: If the uuid of the volume is different than<commit_msg>There is nothing here that uses the config file path.<commit_after># Copyright Hybrid Logic Ltd.  See LICENSE file for details.
# -*- test-case-name: flocker.volume.test.test_service -*-

"""
Volume manager service, the main entry point that manages volumes.
"""

from __future__ import absolute_import

import os
import sys
import json
import stat
from uuid import UUID, uuid4

from zope.interface import Interface, implementer

from characteristic import attributes

from twisted.python.filepath import FilePath
from twisted.application.service import Service
from twisted.internet.endpoints import ProcessEndpoint, connectProtocol
from twisted.internet import reactor
from twisted.internet.defer import fail
from twisted.internet.task import LoopingCall

# We might want to make these utilities shared, rather than in zfs
# module... but in this case the usage is temporary and should go away as
# part of https://github.com/ClusterHQ/flocker/issues/64
from .filesystems.zfs import _AccumulatingProtocol, CommandFailed, StoragePool
from ..common.script import ICommandLineScript

DEFAULT_CONFIG_PATH = FilePath(b"/etc/flocker/volume.json")
FLOCKER_MOUNTPOINT = FilePath(b"/flocker")
FLOCKER_POOL = b"flocker"

WAIT_FOR_VOLUME_INTERVAL = 0.1


class CreateConfigurationError(Exception):
    """Create the configuration file failed."""


class VolumeService(Service):
    """
    Main service for volume management.

    :ivar unicode uuid: A unique identifier for this particular node's
        volume manager. Only available once the service has started.
    """

    def __init__(self, config_path, pool, reactor):
        """
        :param FilePath config_path: Path to the volume manager config file.
        :param pool: An object that is both a
            ``flocker.volume.filesystems.interface.IStoragePool`` provider
            and a ``twisted.application.service.IService`` provider.
        :param reactor: A ``twisted.internet.interface.IReactorTime`` provider.
        """
        self._config_path = config_path
        self.pool = pool
        self._reactor = reactor

    def startService(self):
        Service.startService(self)
        parent = self._config_path.parent()
        try:
            if not parent.exists():
                parent.makedirs()
            if not self._config_path.exists():
                uuid = unicode(uuid4())
                self._config_path.setContent(json.dumps({u"uuid": uuid,
                                                         u"version": 1}))
        except OSError as e:
            raise CreateConfigurationError(e.args[1])
        config = json.loads(self._config_path.getContent())
        self.uuid = config[u"uuid"]
        self.pool.startService()

    def create(self, name):
        """Create a new volume.

        :param unicode name: The name of the volume.

        :return: A ``Deferred`` that fires with a :class:`Volume`.
        """
        volume = Volume(uuid=self.uuid, name=name, service=self)
        d = self.pool.create(volume)

        def created(filesystem):
            filesystem.get_path().chmod(
                # 0o777 the long way:
                stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
            return volume
        d.addCallback(created)
        return d

    def get(self, name):
        """
        Return a locally-owned ``Volume`` with the given name.

        Whether or not this volume actually exists is not checked in any
        way.

        :param unicode name: The name of the volume.

        :param uuid: Either ``None``, in which case the local UUID will be
            used, or a UUID to use for the volume.

        :return: A ``Volume``.
        """
        return Volume(uuid=self.uuid, name=name, service=self)

    def wait_for_volume(self, name):
        """
        Wait for a volume by the given name, owned by thus service, to exist.

        Polls the storage pool for the specified volume to appear.

        :param unicode name: The name of the volume.

        :return: A ``Deferred`` that fires with a :class:`Volume`.
        """
        volume = Volume(uuid=self.uuid, name=name, service=self)

        def check_for_volume(volumes):
            if volume in volumes:
                call.stop()

        def loop():
            d = self.enumerate()
            d.addCallback(check_for_volume)
            return d

        call = LoopingCall(loop)
        call.clock = self._reactor
        d = call.start(WAIT_FOR_VOLUME_INTERVAL)
        d.addCallback(lambda _: volume)
        return d

    def enumerate(self):
        """Get a listing of all volumes managed by this service.

        :return: A ``Deferred`` that fires with an iterator of :class:`Volume`.
        """
        enumerating = self.pool.enumerate()

        def enumerated(filesystems):
            for filesystem in filesystems:
                # XXX It so happens that this works but it's kind of a
                # fragile way to recover the information:
                #    https://github.com/ClusterHQ/flocker/issues/78
                basename = filesystem.get_path().basename()
                try:
                    uuid, name = basename.split(b".", 1)
                    uuid = UUID(uuid)
                except ValueError:
                    # If we can't split on `.` and get two parts then it's not
                    # a filesystem Flocker is managing.  Likewise if we can't
                    # interpret the bit before the `.` as a UUID.  Perhaps a
                    # user created it, who knows.  Just ignore it.
                    continue

                # Probably shouldn't yield this volume if the uuid doesn't
                # match this service's uuid.
                yield Volume(
                    uuid=unicode(uuid),
                    name=name.decode('utf8'),
                    service=self)
        enumerating.addCallback(enumerated)
        return enumerating

    def push(self, volume, destination):
        """
        Push the latest data in the volume to a remote destination.

        This is a blocking API for now.

        Only locally owned volumes (i.e. volumes whose ``uuid`` matches
        this service's) can be pushed.

        :param Volume volume: The volume to push.

        :param IRemoteVolumeManager destination: The remote volume manager
            to push to.

        :raises ValueError: If the uuid of the volume is different than
{'fraction_non_alphanumeric': 0.0827218660693597, 'fraction_numerical': 0.002471615045956592, 'mean_word_length': 2.1260260743602126, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5077294', 'n_tokens_mistral': 3479, 'n_tokens_neox': 3299, 'n_words': 1186}