From: John Spray Date: Mon, 26 Sep 2016 21:17:16 +0000 (+0100) Subject: pybind/mgr: clean up unused code X-Git-Tag: v11.0.1~60^2~10 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=da76c59efc487dee37ee0f4ce518ce4b97878ec7;p=ceph.git pybind/mgr: clean up unused code ...and change the "if False" django-rest-framework conditionals to actual version checks, with a view to genuinely supporting 2.x in future if needed. Signed-off-by: John Spray --- diff --git a/src/pybind/mgr/rest/app/serializers/fields.py b/src/pybind/mgr/rest/app/serializers/fields.py index 743feb7b9522..73ecbbe2c746 100644 --- a/src/pybind/mgr/rest/app/serializers/fields.py +++ b/src/pybind/mgr/rest/app/serializers/fields.py @@ -1,8 +1,13 @@ + +from distutils.version import StrictVersion + +import rest_framework from rest_framework import serializers from rest_framework import fields -if False: + +if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): class BooleanField(serializers.BooleanField): """ Version of BooleanField which handles fields which are 1,0 @@ -17,7 +22,7 @@ else: BooleanField = fields.BooleanField -if False: +if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): class UuidField(serializers.CharField): """ For strings like Ceph service UUIDs and Ceph cluster FSIDs @@ -28,7 +33,7 @@ else: # rest-framework 3 has built in uuid field. UuidField = fields.UUIDField -if False: +if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): class EnumField(serializers.CharField): def __init__(self, mapping, *args, **kwargs): super(EnumField, self).__init__(*args, **kwargs) diff --git a/src/pybind/mgr/rest/app/serializers/v1.py b/src/pybind/mgr/rest/app/serializers/v1.py deleted file mode 100644 index f5f1f777e149..000000000000 --- a/src/pybind/mgr/rest/app/serializers/v1.py +++ /dev/null @@ -1,200 +0,0 @@ - -from django.contrib.auth.models import User -from django.utils import dateformat - -from rest_framework import serializers -import dateutil.parser - - -def to_unix(t): - if t is None: - return None - return int(dateformat.format(t, 'U')) * 1000 - - -class ClusterSerializer(serializers.Serializer): - class Meta: - fields = ('cluster_update_time', 'cluster_update_time_unix', 'id', 'name') - - cluster_update_time = serializers.SerializerMethodField('get_update_time') - name = serializers.Field() - id = serializers.Field() - - # FIXME: we should not be sending out time in two formats: if API consumers want - # unix timestamps they can do the conversion themselves. - cluster_update_time_unix = serializers.SerializerMethodField('get_update_time_unix') - - def get_update_time(self, obj): - return obj.update_time - - def get_update_time_unix(self, obj): - update_time = dateutil.parser.parse(obj.update_time) - return to_unix(update_time) - - # NB calamari 1.0 had cluster_atttempt_time, which no longer makes sense - # because we're listening for events, not polling. TODO: expunge from GUI code. - - -class UserSerializer(serializers.ModelSerializer): - """ - Serializer for the Django User model. - - Used to expose a django-rest-framework user management resource. - """ - class Meta: - model = User - fields = ('id', 'username', 'password', 'email') - - def to_native(self, obj): - # Before conversion, remove the password field. This prevents the hash - # from being displayed when requesting user details. - if 'password' in self.fields: - del self.fields['password'] - return super(UserSerializer, self).to_native(obj) - - def restore_object(self, attrs, instance=None): - user = super(UserSerializer, self).restore_object(attrs, instance) - if user: - # This will perform the Django-specific password obfuscation - user.set_password(attrs['password']) - return user - - -class ClusterSpaceSerializer(serializers.Serializer): - space = serializers.Field() - - class Meta: - fields = ('space',) - - -class ClusterHealthSerializer(serializers.Serializer): - report = serializers.Field() - - class Meta: - fields = ('report', 'cluster_update_time', 'cluster_update_time_unix') - - # FIXME: should not be copying this field onto health counters etc, clients should get - # it by querying the cluster directly. - cluster_update_time = serializers.Field() - cluster_update_time_unix = serializers.SerializerMethodField('get_cluster_update_time_unix') - - def get_cluster_update_time_unix(self, obj): - update_time = dateutil.parser.parse(obj.cluster_update_time) - return to_unix(update_time) - - -class ClusterHealthCountersSerializer(serializers.Serializer): - pg = serializers.SerializerMethodField('get_pg') - mds = serializers.SerializerMethodField('get_mds') - mon = serializers.SerializerMethodField('get_mon') - osd = serializers.SerializerMethodField('get_osd') - - class Meta: - fields = ('pg', 'mds', 'mon', 'osd', 'cluster_update_time', 'cluster_update_time_unix') - - def get_pg(self, obj): - return obj.counters['pg'] - - def get_mds(self, obj): - return obj.counters['mds'] - - def get_mon(self, obj): - return obj.counters['mon'] - - def get_osd(self, obj): - return obj.counters['osd'] - - # FIXME: should not be copying this field onto health counters etc, clients should get - # it by querying the cluster directly. - cluster_update_time = serializers.Field() - cluster_update_time_unix = serializers.SerializerMethodField('get_cluster_update_time_unix') - - def get_cluster_update_time_unix(self, obj): - update_time = dateutil.parser.parse(obj.cluster_update_time) - return to_unix(update_time) - - -class OSDDetailSerializer(serializers.Serializer): - class Meta: - # FIXME: should just be returning the OSD as the object - fields = ('osd',) - - osd = serializers.Field() - - -class OSDListSerializer(serializers.Serializer): - # TODO: the OSD list resource should just return a list, so that - # this serializer class isn't necessary - osds = serializers.Field() - pg_state_counts = serializers.SerializerMethodField('get_pg_state_counts') - - def get_pg_state_counts(self, obj): - return dict((s, len(v)) for s, v in obj.osds_by_pg_state.iteritems()) - - class Meta: - fields = ('osds', 'pg_state_counts') - - -class PoolSerializer(serializers.Serializer): - class Meta: - fields = ('pool_id', 'name', 'quota_max_bytes', 'quota_max_objects', 'used_objects', 'used_bytes', 'id', 'cluster') - - id = serializers.IntegerField() - cluster = serializers.CharField() - pool_id = serializers.IntegerField() - name = serializers.CharField() - quota_max_bytes = serializers.IntegerField() - quota_max_objects = serializers.IntegerField() - used_objects = serializers.IntegerField() - used_bytes = serializers.IntegerField() - - -class ServiceStatusSerializer(serializers.Serializer): - class Meta: - fields = ('type', 'service_id', 'name') - - type = serializers.SerializerMethodField('get_type') - service_id = serializers.SerializerMethodField('get_service_id') - name = serializers.SerializerMethodField('get_name') - - def get_type(self, obj): - return obj['id'][1] - - def get_service_id(self, obj): - return obj['id'][2] - - def get_name(self, obj): - return "%s.%s" % (self.get_type(obj), self.get_service_id(obj)) - - -class ServerSerializer(serializers.Serializer): - class Meta: - fields = ('addr', 'hostname', 'name', 'services') - - services = ServiceStatusSerializer(source='services', many=True) - - addr = serializers.SerializerMethodField('get_addr') - hostname = serializers.CharField() - name = serializers.SerializerMethodField('get_name') - - def get_name(self, obj): - return obj.hostname - - def get_addr(self, obj): - return obj.fqdn - - -class InfoSerializer(serializers.Serializer): - class Meta: - fields = ('version', 'license', 'registered', 'hostname', 'fqdn', 'ipaddr', 'bootstrap_url', 'bootstrap_rhel', - 'bootstrap_ubuntu') - - version = serializers.CharField(help_text="Calamari server version") - license = serializers.CharField(help_text="Calamari license metadata") - registered = serializers.CharField(help_text="Calamari registration metadata") - hostname = serializers.CharField(help_text="Hostname of Calamari server") - fqdn = serializers.CharField(help_text="Fully qualified domain name of Calamari server") - ipaddr = serializers.CharField(help_text="IP address of Calamari server") - bootstrap_url = serializers.CharField(help_text="URL to minion bootstrap script") - bootstrap_rhel = serializers.CharField(help_text="Minion bootstrap command line for Red Hat systems") - bootstrap_ubuntu = serializers.CharField(help_text="Minion bootstrap command line for Ubuntu systems") diff --git a/src/pybind/mgr/rest/app/serializers/v2.py b/src/pybind/mgr/rest/app/serializers/v2.py index 7abe2876f06a..dc9cf65f7e5d 100644 --- a/src/pybind/mgr/rest/app/serializers/v2.py +++ b/src/pybind/mgr/rest/app/serializers/v2.py @@ -1,3 +1,7 @@ + +from distutils.version import StrictVersion + +import rest_framework from rest_framework import serializers import rest.app.serializers.fields as fields from rest.app.types import CRUSH_RULE_TYPE_REPLICATED, \ @@ -6,13 +10,15 @@ from rest.app.types import CRUSH_RULE_TYPE_REPLICATED, \ class ValidatingSerializer(serializers.Serializer): - # django rest framework >= 3 renamed this field @property def init_data(self): + """ + Compatibility alias for django rest framework 2 vs. 3 + """ return self.initial_data def is_valid(self, http_method): - if False: + if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): self._errors = super(ValidatingSerializer, self).errors or {} else: # django rest framework >= 3 has different is_Valid prototype @@ -60,21 +66,6 @@ class ValidatingSerializer(serializers.Serializer): return filtered_data -class ClusterSerializer(serializers.Serializer): - class Meta: - fields = ('update_time', 'id', 'name') - - update_time = serializers.DateTimeField( - help_text="The time at which the last status update from this cluster was received" - ) - name = serializers.Field( - help_text="Human readable cluster name, not a unique identifier" - ) - id = serializers.Field( - help_text="The FSID of the cluster, universally unique" - ) - - class PoolSerializer(ValidatingSerializer): class Meta: fields = ('name', 'id', 'size', 'pg_num', 'crush_ruleset', 'min_size', @@ -257,20 +248,6 @@ class RequestSerializer(serializers.Serializer): help_text="Time at which the request completed, may be null.") -class SaltKeySerializer(ValidatingSerializer): - class Meta: - fields = ('id', 'status') - create_allowed = () - create_required = () - modify_allowed = ('status',) - modify_required = () - - id = serializers.CharField(required=False, - help_text="The minion ID, usually equal to a host's FQDN") - status = serializers.CharField( - help_text="One of 'accepted', 'rejected' or 'pre'") - - class ServiceSerializer(serializers.Serializer): class Meta: fields = ('type', 'id') @@ -293,41 +270,6 @@ class ServerSerializer(serializers.Serializer): help_text="List of Ceph services seen" "on this server") - # Ceph network configuration - # frontend_addr = serializers.CharField() # may be null if no OSDs or mons on server - # backend_addr = serializers.CharField() # may be null if no OSDs on server - - # TODO: reinstate by having OSDs resolve addresses to ifaces and report - # in their metadata - # frontend_iface = serializers.CharField() # may be null if interface for frontend addr not up - # backend_iface = serializers.CharField() # may be null if interface for backend addr not up - - -class EventSerializer(serializers.Serializer): - class Meta: - fields = ('when', 'severity', 'message') - - when = serializers.DateTimeField( - help_text="Time at which event was generated") - severity = serializers.SerializerMethodField('get_severity') - message = serializers.CharField( - help_text="One line human readable description") - - def get_severity(self, obj): - return severity_str(obj.severity) - - -class LogTailSerializer(serializers.Serializer): - """ - Trivial serializer to wrap a string blob of log output - """ - - class Meta: - fields = ('lines',) - - lines = serializers.CharField( - help_text="Retrieved log data as a newline-separated string") - class ConfigSettingSerializer(serializers.Serializer): class Meta: @@ -368,19 +310,17 @@ class CliSerializer(serializers.Serializer): # Declarative metaclass definitions are great until you want # to use a reserved word -if False: +if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): # In django-rest-framework 2.3.x (Calamari used this) OsdSerializer.base_fields['in'] = OsdSerializer.base_fields['_in'] OsdConfigSerializer.base_fields['nodeep-scrub'] = \ OsdConfigSerializer.base_fields['nodeepscrub'] - # django_rest_framework 2.3.12 doesn't let me put help_text on a methodfield - # https://github.com/tomchristie/django-rest-framework/pull/1594 - EventSerializer.base_fields['severity'].help_text = "One of %s" % ",".join( - SEVERITIES.values()) else: OsdSerializer._declared_fields['in'] = OsdSerializer._declared_fields[ '_in'] + del OsdSerializer._declared_fields['_in'] + OsdSerializer._declared_fields['in'].source = "in" OsdConfigSerializer._declared_fields['nodeep-scrub'] = \ OsdConfigSerializer._declared_fields['nodeepscrub'] - EventSerializer._declared_fields[ - 'severity'].help_text = "One of %s" % ",".join(SEVERITIES.values()) + + diff --git a/src/pybind/mgr/rest/app/urls/v2.py b/src/pybind/mgr/rest/app/urls/v2.py index c287c66f8a7d..117d92db3fd6 100644 --- a/src/pybind/mgr/rest/app/urls/v2.py +++ b/src/pybind/mgr/rest/app/urls/v2.py @@ -4,14 +4,9 @@ import rest.app.views.v2 router = routers.DefaultRouter(trailing_slash=False) -# Information about each Ceph cluster (FSID), see sub-URLs - urlpatterns = patterns( '', - # About the host calamari server is running on - # url(r'^grains', rest.app.views.v2.grains), - # This has to come after /user/me to make sure that special case is handled url(r'^', include(router.urls)), @@ -99,21 +94,4 @@ urlpatterns = patterns( rest.app.views.v2.ConfigViewSet.as_view({'get': 'list'})), url(r'^cluster/config/(?P[a-zA-Z0-9_]+)$', rest.app.views.v2.ConfigViewSet.as_view({'get': 'retrieve'})), - - # Events - # url(r'^event$', rest.app.views.v2.EventViewSet.as_view({'get': 'list'})), - # url(r'^cluster/event$', rest.app.views.v2.EventViewSet.as_view({'get': 'list_cluster'})), - # url(r'^server/(?P[a-zA-Z0-9-\.]+)/event$', rest.app.views.v2.EventViewSet.as_view({'get': 'list_server'})), - - # Log tail - # url(r'^cluster/log$', - # rest.app.views.v2.LogTailViewSet.as_view({'get': 'get_cluster_log'})), - # url(r'^server/(?P[a-zA-Z0-9-\.]+)/log$', - # rest.app.views.v2.LogTailViewSet.as_view({'get': 'list_server_logs'})), - # url(r'^server/(?P[a-zA-Z0-9-\.]+)/log/(?P.+)$', - # rest.app.views.v2.LogTailViewSet.as_view({'get': 'get_server_log'})), - - # Ceph CLI access - # url(r'^cluster/cli$', - # rest.app.views.v2.CliViewSet.as_view({'post': 'create'})) ) diff --git a/src/pybind/mgr/rest/app/views/rpc_view.py b/src/pybind/mgr/rest/app/views/rpc_view.py index a776d803e95f..0bceef6a974e 100644 --- a/src/pybind/mgr/rest/app/views/rpc_view.py +++ b/src/pybind/mgr/rest/app/views/rpc_view.py @@ -108,8 +108,8 @@ class MgrClient(object): 'error_message': request.error_message, 'status': request.status, 'headline': request.headline, - 'requested_at': request.requested_at.isoformat(), - 'completed_at': request.completed_at.isoformat() if request.completed_at else None + 'requested_at': request.requested_at, + 'completed_at': request.completed_at } def get_request(self, request_id): diff --git a/src/pybind/mgr/rest/app/views/v2.py b/src/pybind/mgr/rest/app/views/v2.py index 3dacc1990f6c..263807780781 100644 --- a/src/pybind/mgr/rest/app/views/v2.py +++ b/src/pybind/mgr/rest/app/views/v2.py @@ -2,8 +2,10 @@ from collections import defaultdict import json import logging import shlex +from distutils.version import StrictVersion from django.http import Http404 +import rest_framework from rest_framework.exceptions import ParseError, APIException, PermissionDenied from rest_framework.response import Response from rest_framework.decorators import api_view @@ -11,23 +13,17 @@ from rest_framework import status from django.contrib.auth.decorators import login_required -from rest.app.serializers.v2 import PoolSerializer, CrushRuleSetSerializer, CrushRuleSerializer, \ - ServerSerializer, SaltKeySerializer, RequestSerializer, \ - ClusterSerializer, EventSerializer, LogTailSerializer, OsdSerializer, ConfigSettingSerializer, MonSerializer, OsdConfigSerializer, \ - CliSerializer -#from rest.app.views.database_view_set import DatabaseViewSet +from rest.app.serializers.v2 import PoolSerializer, CrushRuleSetSerializer, \ + CrushRuleSerializer, ServerSerializer, RequestSerializer, OsdSerializer, \ + ConfigSettingSerializer, MonSerializer, OsdConfigSerializer from rest.app.views.exceptions import ServiceUnavailable -#from rest.app.views.paginated_mixin import PaginatedMixin -#from rest.app.views.remote_view_set import RemoteViewSet from rest.app.views.rpc_view import RPCViewSet, DataObject -from rest.app.types import CRUSH_RULE, POOL, OSD, USER_REQUEST_COMPLETE, USER_REQUEST_SUBMITTED, \ - OSD_IMPLEMENTED_COMMANDS, MON, OSD_MAP, SYNC_OBJECT_TYPES, ServiceId, severity_from_str, SEVERITIES, \ +from rest.app.types import CRUSH_RULE, POOL, OSD, USER_REQUEST_COMPLETE, \ + USER_REQUEST_SUBMITTED, OSD_IMPLEMENTED_COMMANDS, MON, OSD_MAP, \ + SYNC_OBJECT_TYPES, ServiceId, severity_from_str, SEVERITIES, \ OsdMap, Config, MonMap, MonStatus -class Event(object): - pass - from rest.logger import logger log = logger() @@ -67,10 +63,10 @@ To cancel a request while it is running, send an empty POST to ``request/ 3.x + if StrictVersion(rest_framework.__version__) < StrictVersion("3.0.0"): return Response(self._paginate(request, requests)) else: + # FIXME reinstate pagination, broke in DRF 2.x -> 3.x return Response(requests) @@ -111,76 +107,6 @@ A CRUSH rule is used by Ceph to decide where to locate placement groups on OSDs. return Response(CrushRuleSetSerializer(rulesets, many=True).data) -class SaltKeyViewSet(RPCViewSet): - """ -Ceph servers authentication with the Calamari using a key pair. Before -Calamari accepts messages from a server, the server's key must be accepted. - """ - serializer_class = SaltKeySerializer - - def list(self, request): - return Response(self.serializer_class(self.client.minion_status(None), many=True).data) - - def partial_update(self, request, minion_id): - serializer = self.serializer_class(data=request.DATA) - if serializer.is_valid(request.method): - self._partial_update(minion_id, serializer.get_data()) - return Response(status=status.HTTP_204_NO_CONTENT) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def _partial_update(self, minion_id, data): - valid_status = ['accepted', 'rejected'] - if 'status' not in data: - raise ParseError({'status': "This field is mandatory"}) - elif data['status'] not in valid_status: - raise ParseError({'status': "Must be one of %s" % ",".join(valid_status)}) - else: - key = self.client.minion_get(minion_id) - transition = [key['status'], data['status']] - if transition == ['pre', 'accepted']: - self.client.minion_accept(minion_id) - elif transition == ['pre', 'rejected']: - self.client.minion_reject(minion_id) - else: - raise ParseError({'status': ["Transition {0}->{1} is invalid".format( - transition[0], transition[1] - )]}) - - def _validate_list(self, request): - keys = request.DATA - if not isinstance(keys, list): - raise ParseError("Bulk PATCH must send a list") - for key in keys: - if 'id' not in key: - raise ParseError("Items in bulk PATCH must have 'id' attribute") - - def list_partial_update(self, request): - self._validate_list(request) - - keys = request.DATA - log.debug("KEYS %s" % keys) - for key in keys: - self._partial_update(key['id'], key) - - return Response(status=status.HTTP_204_NO_CONTENT) - - def destroy(self, request, minion_id): - self.client.minion_delete(minion_id) - return Response(status=status.HTTP_204_NO_CONTENT) - - def list_destroy(self, request): - self._validate_list(request) - keys = request.DATA - for key in keys: - self.client.minion_delete(key['id']) - - return Response(status=status.HTTP_204_NO_CONTENT) - - def retrieve(self, request, minion_id): - return Response(self.serializer_class(self.client.minion_get(minion_id)).data) - - class PoolDataObject(DataObject): """ Slightly dressed up version of the raw pool from osd dump @@ -587,93 +513,6 @@ Ceph OSDs, MDSs, mons. many=True).data) -if False: - class EventViewSet(DatabaseViewSet, PaginatedMixin): - """ - Events generated by Calamari server in response to messages from - servers and Ceph clusters. This resource is paginated. - - Note that events are not visible synchronously with respect to - all other API resources. For example, you might read the OSD - map, see an OSD is down, then quickly read the events and find - that the event about the OSD going down is not visible yet (though - it would appear very soon after). - - The ``severity`` attribute mainly follows a typical INFO, WARN, ERROR - hierarchy. However, we have an additional level between INFO and WARN - called RECOVERY. Where something going bad in the system is usually - a WARN message, the opposite state transition is usually a RECOVERY - message. - - This resource supports "more severe than" filtering on the severity - attribute. Pass the desired severity threshold as a URL parameter - in a GET, such as ``?severity=RECOVERY`` to show everything but INFO. - - """ - serializer_class = EventSerializer - - @property - def queryset(self): - return self.session.query(Event).order_by(Event.when.desc()) - - def _filter_by_severity(self, request, queryset=None): - if queryset is None: - queryset = self.queryset - severity_str = request.GET.get("severity", "INFO") - try: - severity = severity_from_str(severity_str) - except KeyError: - raise ParseError("Invalid severity '%s', must be on of %s" % (severity_str, - ",".join(SEVERITIES.values()))) - - return queryset.filter(Event.severity <= severity) - - def list(self, request): - return Response(self._paginate(request, self._filter_by_severity(request))) - - def list_cluster(self, request): - return Response(self._paginate(request, self._filter_by_severity(request, self.queryset.filter_by(fsid=fsid)))) - - def list_server(self, request, fqdn): - return Response(self._paginate(request, self._filter_by_severity(request, self.queryset.filter_by(fqdn=fqdn)))) - - -if False: - class LogTailViewSet(RemoteViewSet): - """ - A primitive remote log viewer. - - Logs are retrieved on demand from the Ceph servers, so this resource will return a 503 error if no suitable - server is available to get the logs. - - GETs take an optional ``lines`` parameter for the number of lines to retrieve. - """ - serializer_class = LogTailSerializer - - def get_cluster_log(self, request): - """ - Retrieve the cluster log from one of a cluster's mons (expect it to be in /var/log/ceph/ceph.log) - """ - - # Number of lines to get - lines = request.GET.get('lines', 40) - - # Resolve FSID to name - name = self.client.get_cluster(fsid)['name'] - - # Execute remote operation synchronously - result = self.run_mon_job("log_tail.tail", ["ceph/{name}.log".format(name=name), lines]) - - return Response({'lines': result}) - - def list_server_logs(self, request, fqdn): - return Response(sorted(self.run_job(fqdn, "log_tail.list_logs", ["."]))) - - def get_server_log(self, request, fqdn, log_path): - lines = request.GET.get('lines', 40) - return Response({'lines': self.run_job(fqdn, "log_tail.tail", [log_path, lines])}) - - class MonViewSet(RPCViewSet): """ Ceph monitor services. @@ -717,54 +556,3 @@ useful to show users data from the /status sub-url, which returns the self.serializer_class([DataObject(m) for m in mons], many=True).data) - -if False: - class CliViewSet(RemoteViewSet): - """ - Access the `ceph` CLI tool remotely. - - To achieve the same result as running "ceph osd dump" at a shell, an - API consumer may POST an object in either of the following formats: - - :: - - {'command': ['osd', 'dump']} - - {'command': 'osd dump'} - - - The response will be a 200 status code if the command executed, regardless - of whether it was successful, to check the result of the command itself - read the ``status`` attribute of the returned data. - - The command will be executed on the first available mon server, retrying - on subsequent mon servers if no response is received. Due to this retry - behaviour, it is possible for the command to be run more than once in - rare cases; since most ceph commands are idempotent this is usually - not a problem. - """ - serializer_class = CliSerializer - - def create(self, request): - # Validate - try: - command = request.DATA['command'] - except KeyError: - raise ParseError("'command' field is required") - else: - if not (isinstance(command, basestring) or isinstance(command, list)): - raise ParseError("'command' must be a string or list") - - # Parse string commands to list - if isinstance(command, basestring): - command = shlex.split(command) - - name = self.client.get_cluster(fsid)['name'] - result = self.run_mon_job("ceph.ceph_command", [name, command]) - log.debug("CliViewSet: result = '%s'" % result) - - if not isinstance(result, dict): - # Errors from salt like "module not available" come back as strings - raise APIException("Remote error: %s" % str(result)) - - return Response(self.serializer_class(DataObject(result)).data)