summaryrefslogtreecommitdiffstats
path: root/src/ceph-volume/ceph_volume/main.py
blob: 7868665cecbf59e32856be3f38462615947f36d1 (plain)
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
from __future__ import print_function
import argparse
import os
import pkg_resources
import sys
import logging

from ceph_volume.decorators import catches
from ceph_volume import log, devices, configuration, conf, exceptions, terminal, inventory, drive_group, activate


class Volume(object):
    _help = """
ceph-volume: Deploy Ceph OSDs using different device technologies like lvm or
physical disks.

Log Path: {log_path}
Ceph Conf: {ceph_path}

{sub_help}
{plugins}
{environ_vars}
{warning}
    """

    def __init__(self, argv=None, parse=True):
        self.mapper = {
            'lvm': devices.lvm.LVM,
            'simple': devices.simple.Simple,
            'raw': devices.raw.Raw,
            'inventory': inventory.Inventory,
            'activate': activate.Activate,
            'drive-group': drive_group.Deploy,
        }
        self.plugin_help = "No plugins found/loaded"
        if argv is None:
            self.argv = sys.argv
        else:
            self.argv = argv
        if parse:
            self.main(self.argv)

    def help(self, warning=False):
        warning = 'See "ceph-volume --help" for full list of options.' if warning else ''
        return self._help.format(
            warning=warning,
            log_path=conf.log_path,
            ceph_path=self.stat_ceph_conf(),
            plugins=self.plugin_help,
            sub_help=terminal.subhelp(self.mapper),
            environ_vars=self.get_environ_vars()
        )

    def get_environ_vars(self):
        environ_vars = []
        for key, value in os.environ.items():
            if key.startswith('CEPH_'):
                environ_vars.append("%s=%s" % (key, value))
        if not environ_vars:
            return ''
        else:
            environ_vars.insert(0, '\nEnviron Variables:')
            return '\n'.join(environ_vars)

    def enable_plugins(self):
        """
        Load all plugins available, add them to the mapper and extend the help
        string with the information from each one
        """
        plugins = _load_library_extensions()
        for plugin in plugins:
            self.mapper[plugin._ceph_volume_name_] = plugin
        self.plugin_help = '\n'.join(['%-19s %s\n' % (
            plugin.name, getattr(plugin, 'help_menu', ''))
            for plugin in plugins])
        if self.plugin_help:
            self.plugin_help = '\nPlugins:\n' + self.plugin_help

    def load_log_path(self):
        conf.log_path = os.getenv('CEPH_VOLUME_LOG_PATH', '/var/log/ceph')

    def stat_ceph_conf(self):
        try:
            configuration.load(conf.path)
            return terminal.green(conf.path)
        except exceptions.ConfigurationError as error:
            return terminal.red(error)

    def _get_split_args(self):
        subcommands = self.mapper.keys()
        slice_on_index = len(self.argv) + 1
        pruned_args = self.argv[1:]
        for count, arg in enumerate(pruned_args):
            if arg in subcommands:
                slice_on_index = count
                break
        return pruned_args[:slice_on_index], pruned_args[slice_on_index:]

    @catches()
    def main(self, argv):
        # these need to be available for the help, which gets parsed super
        # early
        configuration.load_ceph_conf_path()
        self.load_log_path()
        self.enable_plugins()
        main_args, subcommand_args = self._get_split_args()
        # no flags where passed in, return the help menu instead of waiting for
        # argparse which will end up complaning that there are no args
        if len(argv) <= 1:
            print(self.help(warning=True))
            raise SystemExit(0)
        parser = argparse.ArgumentParser(
            prog='ceph-volume',
            formatter_class=argparse.RawDescriptionHelpFormatter,
            description=self.help(),
        )
        parser.add_argument(
            '--cluster',
            default='ceph',
            help='Cluster name (defaults to "ceph")',
        )
        parser.add_argument(
            '--log-level',
            default='debug',
            choices=['debug', 'info', 'warning', 'error', 'critical'],
            help='Change the file log level (defaults to debug)',
        )
        parser.add_argument(
            '--log-path',
            default='/var/log/ceph/',
            help='Change the log path (defaults to /var/log/ceph)',
        )
        args = parser.parse_args(main_args)
        conf.log_path = args.log_path
        if os.path.isdir(conf.log_path):
            conf.log_path = os.path.join(args.log_path, 'ceph-volume.log')
        log.setup(log_level=args.log_level)
        log.setup_console()
        logger = logging.getLogger(__name__)
        logger.info("Running command: ceph-volume %s %s", " ".join(main_args), " ".join(subcommand_args))
        # set all variables from args and load everything needed according to
        # them
        configuration.load_ceph_conf_path(cluster_name=args.cluster)
        try:
            conf.ceph = configuration.load(conf.path)
        except exceptions.ConfigurationError as error:
            # we warn only here, because it is possible that the configuration
            # file is not needed, or that it will be loaded by some other means
            # (like reading from lvm tags)
            logger.warning('ignoring inability to load ceph.conf', exc_info=1)
            terminal.yellow(error)
        # dispatch to sub-commands
        terminal.dispatch(self.mapper, subcommand_args)


def _load_library_extensions():
    """
    Locate all setuptools entry points by the name 'ceph_volume_handlers'
    and initialize them.
    Any third-party library may register an entry point by adding the
    following to their setup.py::

        entry_points = {
            'ceph_volume_handlers': [
                'plugin_name = mylib.mymodule:Handler_Class',
            ],
        },

    `plugin_name` will be used to load it as a sub command.
    """
    logger = logging.getLogger('ceph_volume.plugins')
    group = 'ceph_volume_handlers'
    entry_points = pkg_resources.iter_entry_points(group=group)
    plugins = []
    for ep in entry_points:
        try:
            logger.debug('loading %s' % ep.name)
            plugin = ep.load()
            plugin._ceph_volume_name_ = ep.name
            plugins.append(plugin)
        except Exception as error:
            logger.exception("Error initializing plugin %s: %s" % (ep, error))
    return plugins