Package: ceilometer
Followup-For: Bug #732367

The attached patch fixes this bug by cherry-picking a fix from upstream.
This makes ceilometer work with the version of python-wsme which is
currently in unstable and testing. The same bug was fixed in upstream
havana by pining wsme to 0.5b5, but this version is not available in
Debian.

Gaudenz

-- System Information:
Debian Release: jessie/sid
  APT prefers testing
  APT policy: (800, 'testing'), (700, 'unstable'), (50, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 3.11-2-amd64 (SMP w/2 CPU cores)
Locale: LANG=de_CH.UTF-8, LC_CTYPE=de_CH.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
>From 9e9ab905c55bcfa769144557f266c8c5f5101ccb Mon Sep 17 00:00:00 2001
From: Gaudenz Steinlin <gaud...@debian.org>
Date: Tue, 17 Dec 2013 10:03:03 +0100
Subject: [PATCH] Fix for compatibility with WSME>=0.5b6

Add fix for compatibility with WSME>=0.5b6 taken from upstream commit
e4a1a4fcefd4718e057cf8128c9a6c6b7c98ef59.
---
 debian/changelog                              |   7 +
 debian/patches/WSME-0.5b6-compatibility.patch | 317 ++++++++++++++++++++++++++
 debian/patches/series                         |   1 +
 3 files changed, 325 insertions(+)
 create mode 100644 debian/patches/WSME-0.5b6-compatibility.patch

diff --git a/debian/changelog b/debian/changelog
index 4685714..32f7da9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+ceilometer (2013.2.1-3) UNRELEASED; urgency=medium
+
+  * Add fix for compatibility with WSME>=0.5b6 taken from upstream commit
+    e4a1a4fcefd4718e057cf8128c9a6c6b7c98ef59.
+
+ -- Gaudenz Steinlin <gaud...@debian.org>  Tue, 17 Dec 2013 09:59:52 +0100
+
 ceilometer (2013.2.1-2) unstable; urgency=medium
 
   * Added missing version depends for python-iso8601 (needs >= 0.1.8).
diff --git a/debian/patches/WSME-0.5b6-compatibility.patch b/debian/patches/WSME-0.5b6-compatibility.patch
new file mode 100644
index 0000000..e34fcfe
--- /dev/null
+++ b/debian/patches/WSME-0.5b6-compatibility.patch
@@ -0,0 +1,317 @@
+commit e4a1a4fcefd4718e057cf8128c9a6c6b7c98ef59
+Author: Julien Danjou <jul...@danjou.info>
+Date:   Mon Sep 30 16:43:59 2013 +0200
+
+    api: update for WSME 0.5b6 compliance
+    
+    This makes use of the mandatory option of WSME, that now works, to
+    remove some of our custom validation code. This is needed for new
+    versions of WSME that do more validation on their own.
+    
+    Fixes-Bug: #1240741
+    
+    Change-Id: Icb66d17066b515bebf3f3a326d84e18cbfce01ef
+    Signed-off-by: Julien Danjou <jul...@danjou.info>
+
+--- a/ceilometer/api/controllers/v2.py
++++ b/ceilometer/api/controllers/v2.py
+@@ -516,19 +516,19 @@
+     source = wtypes.text
+     "The ID of the source that identifies where the sample comes from"
+ 
+-    counter_name = wtypes.text
++    counter_name = wsme.wsattr(wtypes.text, mandatory=True)
+     "The name of the meter"
+     # FIXME(dhellmann): Make this meter_name?
+ 
+-    counter_type = wtypes.text
++    counter_type = wsme.wsattr(wtypes.text, mandatory=True)
+     "The type of the meter (see :ref:`measurements`)"
+     # FIXME(dhellmann): Make this meter_type?
+ 
+-    counter_unit = wtypes.text
++    counter_unit = wsme.wsattr(wtypes.text, mandatory=True)
+     "The unit of measure for the value in counter_volume"
+     # FIXME(dhellmann): Make this meter_unit?
+ 
+-    counter_volume = float
++    counter_volume = wsme.wsattr(float, mandatory=True)
+     "The actual measured value"
+ 
+     user_id = wtypes.text
+@@ -537,7 +537,7 @@
+     project_id = wtypes.text
+     "The ID of the project or tenant that owns the resource"
+ 
+-    resource_id = wtypes.text
++    resource_id = wsme.wsattr(wtypes.text, mandatory=True)
+     "The ID of the :class:`Resource` for which the measurements are taken"
+ 
+     timestamp = datetime.datetime
+@@ -561,11 +561,6 @@
+         super(Sample, self).__init__(counter_volume=counter_volume,
+                                      resource_metadata=resource_metadata,
+                                      timestamp=timestamp, **kwds)
+-        # Seems the mandatory option doesn't work so do it manually
+-        for m in ('counter_volume', 'counter_unit',
+-                  'counter_name', 'counter_type', 'resource_id'):
+-            if getattr(self, m) in (wsme.Unset, None):
+-                raise wsme.exc.MissingArgument(m)
+ 
+         if self.resource_metadata in (wtypes.Unset, None):
+             self.resource_metadata = {}
+@@ -709,20 +704,12 @@
+                 for e in pecan.request.storage_conn.get_samples(f, limit=limit)
+                 ]
+ 
+-    @wsme.validate([Sample])
+     @wsme_pecan.wsexpose([Sample], body=[Sample])
+-    def post(self, body):
++    def post(self, samples):
+         """Post a list of new Samples to Ceilometer.
+ 
+-        :param body: a list of samples within the request body.
++        :param samples: a list of samples within the request body.
+         """
+-        # Note:
+-        #  1) the above validate decorator seems to do nothing. LP#1220678
+-        #  2) the mandatory options seems to also do nothing. LP#1227004
+-        #  3) the body should already be in a list of Sample's LP#1233219
+-
+-        samples = [Sample(**b) for b in body]
+-
+         now = timeutils.utcnow()
+         auth_project = acl.get_limited_to_project(pecan.request.headers)
+         def_source = pecan.request.cfg.sample_source
+@@ -1002,14 +989,6 @@
+ 
+     @staticmethod
+     def validate(threshold_rule):
+-        #note(sileht): wsme mandatory doesn't work as expected
+-        #workaround for https://bugs.launchpad.net/wsme/+bug/1227004
+-        for field in ['meter_name', 'threshold']:
+-            if not getattr(threshold_rule, field):
+-                error = _("threshold_rule/%s is mandatory") % field
+-                pecan.response.translatable_error = error
+-                raise wsme.exc.ClientSideError(unicode(error))
+-
+         #note(sileht): wsme default doesn't work in some case
+         #workaround for https://bugs.launchpad.net/wsme/+bug/1227039
+         if not threshold_rule.query:
+@@ -1074,17 +1053,6 @@
+                    alarm_ids=['739e99cb-c2ec-4718-b900-332502355f38',
+                               '153462d0-a9b8-4b5b-8175-9e4b05e9b856'])
+ 
+-    @staticmethod
+-    def validate(combination_rule):
+-        #note(sileht): wsme mandatory doesn't works as expected
+-        #workaround for https://bugs.launchpad.net/wsme/+bug/1227004
+-        if not combination_rule.alarm_ids:
+-            error = _("combination_rule/alarm_ids is mandatory")
+-            pecan.response.translatable_error = error
+-            raise wsme.exc.ClientSideError(unicode(error))
+-
+-        return combination_rule
+-
+ 
+ class Alarm(_Base):
+     """Representation of an alarm.
+@@ -1165,13 +1133,18 @@
+ 
+     @staticmethod
+     def validate(alarm):
+-        #note(sileht): wsme mandatory doesn't work as expected
+-        #workaround for https://bugs.launchpad.net/wsme/+bug/1227004
+-        for field in ['name', 'type']:
+-            if not getattr(alarm, field):
+-                error = _("%s is mandatory") % field
+-                pecan.response.translatable_error = error
+-                raise wsme.exc.ClientSideError(unicode(error))
++        if (alarm.threshold_rule == wtypes.Unset
++                and alarm.combination_rule == wtypes.Unset):
++            error = _("either threshold_rule or combination_rule "
++                      "must be set")
++            pecan.response.translatable_error = error
++            raise wsme.exc.ClientSideError(unicode(error))
++
++        if alarm.threshold_rule and alarm.combination_rule:
++            error = _("threshold_rule and combination_rule "
++                      "cannot be set at the same time")
++            pecan.response.translatable_error = error
++            raise wsme.exc.ClientSideError(unicode(error))
+ 
+         if alarm.threshold_rule:
+             # ensure an implicit constraint on project_id is added to
+@@ -1182,20 +1155,17 @@
+                 on_behalf_of=alarm.project_id
+             )
+         elif alarm.combination_rule:
+-            auth_project = _get_auth_project(alarm.project_id)
++            project = _get_auth_project(alarm.project_id
++                                        if alarm.project_id != wtypes.Unset
++                                        else None)
+             for id in alarm.combination_rule.alarm_ids:
+                 alarms = list(pecan.request.storage_conn.get_alarms(
+-                    alarm_id=id, project=auth_project))
++                    alarm_id=id, project=project))
+                 if not alarms:
+                     error = _("Alarm %s doesn't exist") % id
+                     pecan.response.translatable_error = error
+                     raise wsme.exc.ClientSideError(unicode(error))
+ 
+-        if alarm.threshold_rule and alarm.combination_rule:
+-            error = _("threshold_rule and combination_rule "
+-                      "cannot be set at the same time")
+-            pecan.response.translatable_error = error
+-            raise wsme.exc.ClientSideError(unicode(error))
+         return alarm
+ 
+     @classmethod
+@@ -1318,7 +1288,6 @@
+         """Return this alarm."""
+         return Alarm.from_db_model(self._alarm())
+ 
+-    @wsme.validate(Alarm)
+     @wsme_pecan.wsexpose(Alarm, wtypes.text, body=Alarm)
+     def put(self, data):
+         """Modify this alarm."""
+@@ -1329,18 +1298,20 @@
+ 
+         data.alarm_id = self._id
+         user, project = acl.get_limited_to(pecan.request.headers)
+-        data.user_id = user or data.user_id or alarm_in.user_id
+-        data.project_id = project or data.project_id or alarm_in.project_id
++        if user:
++            data.user_id = user
++        elif data.user_id == wtypes.Unset:
++            data.user_id = alarm_in.user_id
++        if project:
++            data.project_id = project
++        elif data.project_id == wtypes.Unset:
++            data.project_id = alarm_in.project_id
+         data.timestamp = now
+         if alarm_in.state != data.state:
+             data.state_timestamp = now
+         else:
+             data.state_timestamp = alarm_in.state_timestamp
+ 
+-        #note(sileht): workaround for
+-        #https://bugs.launchpad.net/wsme/+bug/1220678
+-        Alarm.validate(data)
+-
+         old_alarm = Alarm.from_db_model(alarm_in).as_dict(storage.models.Alarm)
+         updated_alarm = data.as_dict(storage.models.Alarm)
+         try:
+@@ -1440,7 +1411,6 @@
+         except NotImplementedError:
+             pass
+ 
+-    @wsme.validate(Alarm)
+     @wsme_pecan.wsexpose(Alarm, body=Alarm, status_code=201)
+     def post(self, data):
+         """Create a new alarm."""
+@@ -1449,17 +1419,17 @@
+ 
+         data.alarm_id = str(uuid.uuid4())
+         user, project = acl.get_limited_to(pecan.request.headers)
+-        data.user_id = (user or data.user_id or
+-                        pecan.request.headers.get('X-User-Id'))
+-        data.project_id = (project or data.project_id or
+-                           pecan.request.headers.get('X-Project-Id'))
++        if user:
++            data.user_id = user
++        elif data.user_id == wtypes.Unset:
++            data.user_id = pecan.request.headers.get('X-User-Id')
++        if project:
++            data.project_id = project
++        elif data.project_id == wtypes.Unset:
++            data.project_id = pecan.request.headers.get('X-Project-Id')
+         data.timestamp = now
+         data.state_timestamp = now
+ 
+-        #note(sileht): workaround for
+-        #https://bugs.launchpad.net/wsme/+bug/1220678
+-        Alarm.validate(data)
+-
+         change = data.as_dict(storage.models.Alarm)
+ 
+         # make sure alarms are unique by name per project.
+--- a/ceilometer/tests/api.py
++++ b/ceilometer/tests/api.py
+@@ -104,6 +104,9 @@
+                 'template_path': '%s/ceilometer/api/templates' % root_dir,
+                 'enable_acl': enable_acl,
+             },
++            'wsme': {
++                'debug': True,
++            },
+         }
+ 
+         return pecan.testing.load_test_app(self.config)
+--- a/tests/api/v2/test_alarm_scenarios.py
++++ b/tests/api/v2/test_alarm_scenarios.py
+@@ -278,7 +278,9 @@
+                                   status=400, headers=self.auth_headers)
+             self.assertEqual(
+                 resp.json['error_message']['faultstring'],
+-                '%s is mandatory' % field)
++                "Invalid input for field/attribute %s."
++                " Value: \'None\'. Mandatory field missing."
++                % field.split('/', 1)[-1])
+         alarms = list(self.conn.get_alarms())
+         self.assertEqual(4, len(alarms))
+ 
+--- a/tests/api/v2/test_app.py
++++ b/tests/api/v2/test_app.py
+@@ -134,7 +134,8 @@
+         # Ensure translated messages get placed properly into json faults
+         self.stubs.Set(gettextutils, 'get_localized_message',
+                        self._fake_get_localized_message)
+-        response = self.post_json('/alarms', params={},
++        response = self.post_json('/alarms', params={'name': 'foobar',
++                                                     'type': 'threshold'},
+                                   expect_errors=True,
+                                   headers={"Accept":
+                                            "application/json"}
+@@ -169,7 +170,8 @@
+         self.stubs.Set(gettextutils, 'get_localized_message',
+                        self._fake_get_localized_message)
+ 
+-        response = self.post_json('/alarms', params={},
++        response = self.post_json('/alarms', params={'name': 'foobar',
++                                                     'type': 'threshold'},
+                                   expect_errors=True,
+                                   headers={"Accept":
+                                            "application/xml,*/*"}
+@@ -186,7 +188,8 @@
+         self.stubs.Set(gettextutils, 'get_localized_message',
+                        self._fake_get_localized_message)
+ 
+-        response = self.post_json('/alarms', params={},
++        response = self.post_json('/alarms', params={'name': 'foobar',
++                                                     'type': 'threshold'},
+                                   expect_errors=True,
+                                   headers={"Accept":
+                                            "application/xml,*/*",
+--- a/tests/api/v2/test_post_samples_scenarios.py
++++ b/tests/api/v2/test_post_samples_scenarios.py
+@@ -198,7 +198,7 @@
+             s_broke = copy.copy(s1)
+             del s_broke[0][m]
+             print('posting without %s' % m)
+-            data = self.post_json('/meters/my_counter_name/', s_broke,
++            data = self.post_json('/meters/my_counter_name', s_broke,
+                                   expect_errors=True)
+             self.assertEqual(data.status_int, 400)
+ 
+--- a/requirements.txt
++++ b/requirements.txt
+@@ -22,7 +22,7 @@
+ lxml>=2.3
+ requests>=1.1
+ six>=1.4.1
+-WSME>=0.5b5,<0.5b6
++WSME>=0.5b6
+ PyYAML>=3.1.0
+ oslo.config>=1.2.0
+ happybase>=0.4
diff --git a/debian/patches/series b/debian/patches/series
index 31b8fb8..dc4e9d0 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,2 @@
 removes-sqlalchemy-restriction.patch
+WSME-0.5b6-compatibility.patch
-- 
1.8.5.1

Reply via email to