blob: 0f8c6b437dca0205a0df79901042cb0dd7cb08c0 [file] [log] [blame]
Srikanth Vavilapallia9bbe6f2016-08-09 00:17:22 +00001import requests
2from six.moves import urllib
3import urllib2
4import pytz
5import datetime
6import time
7from rest_framework.decorators import api_view
8from rest_framework.response import Response
9from rest_framework.reverse import reverse
10from rest_framework import serializers
11from rest_framework import generics
12from rest_framework.views import APIView
13from core.models import *
14from services.monitoring.models import MonitoringChannel, CeilometerService
15from django.forms import widgets
16from django.utils import datastructures
17from django.utils.translation import ugettext_lazy as _
18from django.utils import timezone
19from django.core.exceptions import PermissionDenied
20from xos.logger import observer_logger as logger
21
22# This REST API endpoint provides information that the ceilometer view needs to display
23
24def getTenantCeilometerProxyURL(user):
25 monitoring_channel = None
26 for obj in MonitoringChannel.get_tenant_objects().all():
27 if (obj.creator.username == user.username):
28 monitoring_channel = obj
29 break
30 if not monitoring_channel:
31 raise XOSMissingField("Monitoring channel is missing for this tenant...Create one and invoke this REST API")
32 #TODO: Wait until URL is completely UP
33 MAX_ATTEMPTS = 5
34 attempts = 0
35 while True:
36 try:
37 response = urllib2.urlopen(monitoring_channel.ceilometer_url)
38 break
39 except urllib2.HTTPError, e:
40 logger.info('HTTP error %(reason)s' % {'reason':e.reason})
41 break
42 except urllib2.URLError, e:
43 attempts += 1
44 if attempts >= MAX_ATTEMPTS:
45 raise XOSServiceUnavailable("Ceilometer channel is not ready yet...Try again later")
46 logger.info('URL error %(reason)s' % {'reason':e.reason})
47 time.sleep(1)
48 pass
49 logger.info("Ceilometer proxy URL for user %(user)s is %(url)s" % {'user':user.username,'url':monitoring_channel.ceilometer_url})
50 return monitoring_channel.ceilometer_url
51
52def getTenantControllerTenantMap(user, slice=None):
53 tenantmap={}
54 if not slice:
55 slices = Slice.objects.filter(creator=user)
56 else:
57 slices = [slice]
58 for s in slices:
59 for cs in s.controllerslices.all():
60 if cs.tenant_id:
61 tenantmap[cs.tenant_id] = {"slice": cs.slice.name}
62 if cs.slice.service:
63 tenantmap[cs.tenant_id]["service"] = cs.slice.service.name
64 else:
65 logger.warn("SRIKANTH: Slice %(slice)s is not associated with any service" % {'slice':cs.slice.name})
66 tenantmap[cs.tenant_id]["service"] = "Other"
67 if not slice:
68 #TEMPORARY WORK AROUND: There are some resource in network like whitebox switches does not belong to a specific tenant.
69 #They are all associated with "default_admin_tenant" tenant
70 tenantmap["default_admin_tenant"] = {"slice": "default_admin_tenant", "service": "Other"}
71 return tenantmap
72
73def build_url(path, q, params=None):
74 """Convert list of dicts and a list of params to query url format.
75
76 This will convert the following:
77 "[{field=this,op=le,value=34},
78 {field=that,op=eq,value=foo,type=string}],
79 ['foo=bar','sna=fu']"
80 to:
81 "?q.field=this&q.field=that&
82 q.op=le&q.op=eq&
83 q.type=&q.type=string&
84 q.value=34&q.value=foo&
85 foo=bar&sna=fu"
86 """
87 if q:
88 query_params = {'q.field': [],
89 'q.value': [],
90 'q.op': [],
91 'q.type': []}
92
93 for query in q:
94 for name in ['field', 'op', 'value', 'type']:
95 query_params['q.%s' % name].append(query.get(name, ''))
96
97 # Transform the dict to a sequence of two-element tuples in fixed
98 # order, then the encoded string will be consistent in Python 2&3.
99 new_qparams = sorted(query_params.items(), key=lambda x: x[0])
100 path += "?" + urllib.parse.urlencode(new_qparams, doseq=True)
101
102 if params:
103 for p in params:
104 path += '&%s' % p
105 elif params:
106 path += '?%s' % params[0]
107 for p in params[1:]:
108 path += '&%s' % p
109 return path
110
111def concat_url(endpoint, url):
112 """Concatenate endpoint and final URL.
113
114 E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
115 "http://keystone/v2.0/tokens".
116
117 :param endpoint: the base URL
118 :param url: the final URL
119 """
120 return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
121
122def resource_list(request, query=None, ceilometer_url=None, ceilometer_usage_object=None):
123 """List the resources."""
124 url = concat_url(ceilometer_url, build_url('/v2/resources', query))
125 try:
126 response = requests.get(url)
127 except requests.exceptions.RequestException as e:
128 raise e
129 return response.json()
130
131def sample_list(request, meter_name, ceilometer_url=None, query=None, limit=None):
132 """List the samples for this meters."""
133 params = ['limit=%s' % limit] if limit else []
134 url = concat_url(ceilometer_url, build_url('/v2/samples', query, params))
135 try:
136 response = requests.get(url)
137 except requests.exceptions.RequestException as e:
138 raise e
139 return response.json()
140
141def meter_list(request, ceilometer_url=None, query=None):
142 """List the user's meters."""
143 url = concat_url(ceilometer_url, build_url('/v2/meters', query))
144 try:
145 response = requests.get(url)
146 except requests.exceptions.RequestException as e:
147 raise e
148 return response.json()
149
150
151def statistic_list(request, meter_name, ceilometer_url=None, query=None, period=None):
152 """List of statistics."""
153 p = ['period=%s' % period] if period else []
154 url = concat_url(ceilometer_url, build_url('/v2/meters/' + meter_name + '/statistics', query, p))
155 try:
156 response = requests.get(url)
157 except requests.exceptions.RequestException as e:
158 raise e
159 return response.json()
160
161def diff_lists(a, b):
162 if not a:
163 return []
164 elif not b:
165 return a
166 else:
167 return list(set(a) - set(b))
168
169def get_resource_map(request, ceilometer_url, query=None):
170 resource_map = {}
171 try:
172 resources = resource_list(request, ceilometer_url=ceilometer_url, query=query)
173 for r in resources:
174 if 'display_name' in r['metadata']:
175 name = r['metadata']['display_name']
176 elif 'name' in r['metadata']:
177 name = r['metadata']['name']
178 else:
179 name = r['resource_id']
180 resource_map[r['resource_id']] = name
181 except requests.exceptions.RequestException as e:
182 raise e
183
184 return resource_map
185
186class Meters(object):
187 """Class for listing of available meters.
188
189 It is listing meters defined in this class that are available
190 in Ceilometer meter_list.
191
192 It is storing information that is not available in Ceilometer, i.e.
193 label, description.
194
195 """
196
197 def __init__(self, request=None, ceilometer_meter_list=None, ceilometer_url=None, query=None, tenant_map=None, resource_map=None):
198 # Storing the request.
199 self._request = request
200 self.ceilometer_url = ceilometer_url
201 self.tenant_map = tenant_map
202 self.resource_map = resource_map
203
204 # Storing the Ceilometer meter list
205 if ceilometer_meter_list:
206 self._ceilometer_meter_list = ceilometer_meter_list
207 else:
208 try:
209 meter_query=[]
210 if query:
211 meter_query = query
212 self._ceilometer_meter_list = meter_list(request, self.ceilometer_url, meter_query)
213 except requests.exceptions.RequestException as e:
214 self._ceilometer_meter_list = []
215 raise e
216
217 # Storing the meters info categorized by their services.
218 self._nova_meters_info = self._get_nova_meters_info()
219 self._neutron_meters_info = self._get_neutron_meters_info()
220 self._glance_meters_info = self._get_glance_meters_info()
221 self._cinder_meters_info = self._get_cinder_meters_info()
222 self._swift_meters_info = self._get_swift_meters_info()
223 self._kwapi_meters_info = self._get_kwapi_meters_info()
224 self._ipmi_meters_info = self._get_ipmi_meters_info()
225 self._vcpe_meters_info = self._get_vcpe_meters_info()
226 self._volt_meters_info = self._get_volt_meters_info()
227 self._sdn_meters_info = self._get_sdn_meters_info()
228
229 # Storing the meters info of all services together.
230 all_services_meters = (self._nova_meters_info,
231 self._neutron_meters_info,
232 self._glance_meters_info,
233 self._cinder_meters_info,
234 self._swift_meters_info,
235 self._kwapi_meters_info,
236 self._ipmi_meters_info,
237 self._vcpe_meters_info,
238 self._volt_meters_info,
239 self._sdn_meters_info)
240 self._all_meters_info = {}
241 for service_meters in all_services_meters:
242 self._all_meters_info.update(dict([(meter_name, meter_info)
243 for meter_name, meter_info
244 in service_meters.items()]))
245
246 # Here will be the cached Meter objects, that will be reused for
247 # repeated listing.
248 self._cached_meters = {}
249
250 def list_all(self, only_meters=None, except_meters=None):
251 """Returns a list of meters based on the meters names.
252
253 :Parameters:
254 - `only_meters`: The list of meter names we want to show.
255 - `except_meters`: The list of meter names we don't want to show.
256 """
257
258 return self._list(only_meters=only_meters,
259 except_meters=except_meters)
260
261 def list_nova(self, except_meters=None):
262 """Returns a list of meters tied to nova.
263
264 :Parameters:
265 - `except_meters`: The list of meter names we don't want to show.
266 """
267
268 return self._list(only_meters=self._nova_meters_info.keys(),
269 except_meters=except_meters)
270
271 def list_neutron(self, except_meters=None):
272 """Returns a list of meters tied to neutron.
273
274 :Parameters:
275 - `except_meters`: The list of meter names we don't want to show.
276 """
277
278 return self._list(only_meters=self._neutron_meters_info.keys(),
279 except_meters=except_meters)
280
281 def list_glance(self, except_meters=None):
282 """Returns a list of meters tied to glance.
283
284 :Parameters:
285 - `except_meters`: The list of meter names we don't want to show.
286 """
287
288 return self._list(only_meters=self._glance_meters_info.keys(),
289 except_meters=except_meters)
290
291 def list_cinder(self, except_meters=None):
292 """Returns a list of meters tied to cinder.
293
294 :Parameters:
295 - `except_meters`: The list of meter names we don't want to show.
296 """
297
298 return self._list(only_meters=self._cinder_meters_info.keys(),
299 except_meters=except_meters)
300
301 def list_swift(self, except_meters=None):
302 """Returns a list of meters tied to swift.
303
304 :Parameters:
305 - `except_meters`: The list of meter names we don't want to show.
306 """
307
308 return self._list(only_meters=self._swift_meters_info.keys(),
309 except_meters=except_meters)
310
311 def list_kwapi(self, except_meters=None):
312 """Returns a list of meters tied to kwapi.
313
314 :Parameters:
315 - `except_meters`: The list of meter names we don't want to show.
316 """
317
318 return self._list(only_meters=self._kwapi_meters_info.keys(),
319 except_meters=except_meters)
320
321 def list_ipmi(self, except_meters=None):
322 """Returns a list of meters tied to ipmi
323
324 :Parameters:
325 - `except_meters`: The list of meter names we don't want to show
326 """
327
328 return self._list(only_meters=self._ipmi_meters_info.keys(),
329 except_meters=except_meters)
330
331 def list_vcpe(self, except_meters=None):
332 """Returns a list of meters tied to vcpe service
333
334 :Parameters:
335 - `except_meters`: The list of meter names we don't want to show
336 """
337
338 return self._list(only_meters=self._vcpe_meters_info.keys(),
339 except_meters=except_meters)
340
341 def list_volt(self, except_meters=None):
342 """Returns a list of meters tied to volt service
343
344 :Parameters:
345 - `except_meters`: The list of meter names we don't want to show
346 """
347
348 return self._list(only_meters=self._volt_meters_info.keys(),
349 except_meters=except_meters)
350
351 def list_sdn(self, except_meters=None):
352 """Returns a list of meters tied to sdn service
353
354 :Parameters:
355 - `except_meters`: The list of meter names we don't want to show
356 """
357
358 return self._list(only_meters=self._sdn_meters_info.keys(),
359 except_meters=except_meters)
360
361 def list_other_services(self, except_meters=None):
362 """Returns a list of meters tied to ipmi
363
364 :Parameters:
365 - `except_meters`: The list of meter names we don't want to show
366 """
367 other_service_meters = [m for m in self._ceilometer_meter_list
368 if m.name not in self._all_meters_info.keys()]
369 other_service_meters = diff_lists(other_service_meters, except_meters)
370
371 meters = []
372 for meter in other_service_meters:
373 self._cached_meters[meter.name] = meter
374 meters.append(meter)
375 return meters
376
377 def _list(self, only_meters=None, except_meters=None):
378 """Returns a list of meters based on the meters names.
379
380 :Parameters:
381 - `only_meters`: The list of meter names we want to show.
382 - `except_meters`: The list of meter names we don't want to show.
383 """
384
385 # Get all wanted meter names.
386 if only_meters:
387 meter_names = only_meters
388 else:
389 meter_names = [meter_name for meter_name
390 in self._all_meters_info.keys()]
391
392 meter_names = diff_lists(meter_names, except_meters)
393 # Collect meters for wanted meter names.
394 return self._get_meters(meter_names)
395
396 def _get_meters(self, meter_names):
397 """Obtain meters based on meter_names.
398
399 The meters that do not exist in Ceilometer meter list are left out.
400
401 :Parameters:
402 - `meter_names`: A list of meter names we want to fetch.
403 """
404
405 meters = []
406 for meter_name in meter_names:
407 meter_candidates = self._get_meter(meter_name)
408 if meter_candidates:
409 meters.extend(meter_candidates)
410 return meters
411
412 def _get_meter(self, meter_name):
413 """Obtains a meter.
414
415 Obtains meter either from cache or from Ceilometer meter list
416 joined with statically defined meter info like label and description.
417
418 :Parameters:
419 - `meter_name`: A meter name we want to fetch.
420 """
421 meter_candidates = self._cached_meters.get(meter_name, None)
422 if not meter_candidates:
423 meter_candidates = [m for m in self._ceilometer_meter_list
424 if m["name"] == meter_name]
425
426 if meter_candidates:
427 meter_info = self._all_meters_info.get(meter_name, None)
428 if meter_info:
429 label = meter_info["label"]
430 description = meter_info["description"]
431 meter_category = meter_info["type"]
432 else:
433 label = ""
434 description = ""
435 meter_category = "Other"
436 for meter in meter_candidates:
437 meter["label"] = label
438 meter["description"] = description
439 meter["category"] = meter_category
440 if meter["project_id"] in self.tenant_map.keys():
441 meter["slice"] = self.tenant_map[meter["project_id"]]["slice"]
442 meter["service"] = self.tenant_map[meter["project_id"]]["service"]
443 else:
444 meter["slice"] = meter["project_id"]
445 meter["service"] = "Other"
446 if meter["resource_id"] in self.resource_map.keys():
447 meter["resource_name"] = self.resource_map[meter["resource_id"]]
448
449 self._cached_meters[meter_name] = meter_candidates
450
451 return meter_candidates
452
453 def _get_nova_meters_info(self):
454 """Returns additional info for each meter.
455
456 That will be used for augmenting the Ceilometer meter.
457 """
458
459 # TODO(lsmola) Unless the Ceilometer will provide the information
460 # below, I need to define it as a static here. I will be joining this
461 # to info that I am able to obtain from Ceilometer meters, hopefully
462 # some day it will be supported all.
463 meters_info = datastructures.SortedDict([
464 ("instance", {
465 'type': _("Nova"),
466 'label': '',
467 'description': _("Existence of instance"),
468 }),
469 ("instance:<type>", {
470 'type': _("Nova"),
471 'label': '',
472 'description': _("Existence of instance <type> "
473 "(openstack types)"),
474 }),
475 ("memory", {
476 'type': _("Nova"),
477 'label': '',
478 'description': _("Volume of RAM"),
479 }),
480 ("memory.usage", {
481 'type': _("Nova"),
482 'label': '',
483 'description': _("Volume of RAM used"),
484 }),
485 ("cpu", {
486 'type': _("Nova"),
487 'label': '',
488 'description': _("CPU time used"),
489 }),
490 ("cpu_util", {
491 'type': _("Nova"),
492 'label': '',
493 'description': _("Average CPU utilization"),
494 }),
495 ("vcpus", {
496 'type': _("Nova"),
497 'label': '',
498 'description': _("Number of VCPUs"),
499 }),
500 ("disk.read.requests", {
501 'type': _("Nova"),
502 'label': '',
503 'description': _("Number of read requests"),
504 }),
505 ("disk.write.requests", {
506 'type': _("Nova"),
507 'label': '',
508 'description': _("Number of write requests"),
509 }),
510 ("disk.read.bytes", {
511 'type': _("Nova"),
512 'label': '',
513 'description': _("Volume of reads"),
514 }),
515 ("disk.write.bytes", {
516 'type': _("Nova"),
517 'label': '',
518 'description': _("Volume of writes"),
519 }),
520 ("disk.read.requests.rate", {
521 'type': _("Nova"),
522 'label': '',
523 'description': _("Average rate of read requests"),
524 }),
525 ("disk.write.requests.rate", {
526 'type': _("Nova"),
527 'label': '',
528 'description': _("Average rate of write requests"),
529 }),
530 ("disk.read.bytes.rate", {
531 'type': _("Nova"),
532 'label': '',
533 'description': _("Average rate of reads"),
534 }),
535 ("disk.write.bytes.rate", {
536 'type': _("Nova"),
537 'label': '',
538 'description': _("Average volume of writes"),
539 }),
540 ("disk.root.size", {
541 'type': _("Nova"),
542 'label': '',
543 'description': _("Size of root disk"),
544 }),
545 ("disk.ephemeral.size", {
546 'type': _("Nova"),
547 'label': '',
548 'description': _("Size of ephemeral disk"),
549 }),
550 ("network.incoming.bytes", {
551 'type': _("Nova"),
552 'label': '',
553 'description': _("Number of incoming bytes "
554 "on the network for a VM interface"),
555 }),
556 ("network.outgoing.bytes", {
557 'type': _("Nova"),
558 'label': '',
559 'description': _("Number of outgoing bytes "
560 "on the network for a VM interface"),
561 }),
562 ("network.incoming.packets", {
563 'type': _("Nova"),
564 'label': '',
565 'description': _("Number of incoming "
566 "packets for a VM interface"),
567 }),
568 ("network.outgoing.packets", {
569 'type': _("Nova"),
570 'label': '',
571 'description': _("Number of outgoing "
572 "packets for a VM interface"),
573 }),
574 ("network.incoming.bytes.rate", {
575 'type': _("Nova"),
576 'label': '',
577 'description': _("Average rate per sec of incoming "
578 "bytes on a VM network interface"),
579 }),
580 ("network.outgoing.bytes.rate", {
581 'type': _("Nova"),
582 'label': '',
583 'description': _("Average rate per sec of outgoing "
584 "bytes on a VM network interface"),
585 }),
586 ("network.incoming.packets.rate", {
587 'type': _("Nova"),
588 'label': '',
589 'description': _("Average rate per sec of incoming "
590 "packets on a VM network interface"),
591 }),
592 ("network.outgoing.packets.rate", {
593 'type': _("Nova"),
594 'label': '',
595 'description': _("Average rate per sec of outgoing "
596 "packets on a VM network interface"),
597 }),
598 ])
599 # Adding flavor based meters into meters_info dict
600 # TODO(lsmola) this kind of meter will be probably deprecated
601 # https://bugs.launchpad.net/ceilometer/+bug/1208365 . Delete it then.
602 #for flavor in get_flavor_names(self._request):
603 # name = 'instance:%s' % flavor
604 # meters_info[name] = dict(meters_info["instance:<type>"])
605
606 # meters_info[name]['description'] = (
607 # _('Duration of instance type %s (openstack flavor)') %
608 # flavor)
609
610 # TODO(lsmola) allow to set specific in local_settings. For all meters
611 # because users can have their own agents and meters.
612 return meters_info
613
614 def _get_neutron_meters_info(self):
615 """Returns additional info for each meter.
616
617 That will be used for augmenting the Ceilometer meter.
618 """
619
620 # TODO(lsmola) Unless the Ceilometer will provide the information
621 # below, I need to define it as a static here. I will be joining this
622 # to info that I am able to obtain from Ceilometer meters, hopefully
623 # some day it will be supported all.
624 return datastructures.SortedDict([
625 ('network', {
626 'type': _("Neutron"),
627 'label': '',
628 'description': _("Existence of network"),
629 }),
630 ('network.create', {
631 'type': _("Neutron"),
632 'label': '',
633 'description': _("Creation requests for this network"),
634 }),
635 ('network.update', {
636 'type': _("Neutron"),
637 'label': '',
638 'description': _("Update requests for this network"),
639 }),
640 ('subnet', {
641 'type': _("Neutron"),
642 'label': '',
643 'description': _("Existence of subnet"),
644 }),
645 ('subnet.create', {
646 'type': _("Neutron"),
647 'label': '',
648 'description': _("Creation requests for this subnet"),
649 }),
650 ('subnet.update', {
651 'type': _("Neutron"),
652 'label': '',
653 'description': _("Update requests for this subnet"),
654 }),
655 ('port', {
656 'type': _("Neutron"),
657 'label': '',
658 'description': _("Existence of port"),
659 }),
660 ('port.create', {
661 'type': _("Neutron"),
662 'label': '',
663 'description': _("Creation requests for this port"),
664 }),
665 ('port.update', {
666 'type': _("Neutron"),
667 'label': '',
668 'description': _("Update requests for this port"),
669 }),
670 ('router', {
671 'type': _("Neutron"),
672 'label': '',
673 'description': _("Existence of router"),
674 }),
675 ('router.create', {
676 'type': _("Neutron"),
677 'label': '',
678 'description': _("Creation requests for this router"),
679 }),
680 ('router.update', {
681 'type': _("Neutron"),
682 'label': '',
683 'description': _("Update requests for this router"),
684 }),
685 ('ip.floating', {
686 'type': _("Neutron"),
687 'label': '',
688 'description': _("Existence of floating ip"),
689 }),
690 ('ip.floating.create', {
691 'type': _("Neutron"),
692 'label': '',
693 'description': _("Creation requests for this floating ip"),
694 }),
695 ('ip.floating.update', {
696 'type': _("Neutron"),
697 'label': '',
698 'description': _("Update requests for this floating ip"),
699 }),
700 ])
701
702 def _get_glance_meters_info(self):
703 """Returns additional info for each meter.
704
705 That will be used for augmenting the Ceilometer meter.
706 """
707
708 # TODO(lsmola) Unless the Ceilometer will provide the information
709 # below, I need to define it as a static here. I will be joining this
710 # to info that I am able to obtain from Ceilometer meters, hopefully
711 # some day it will be supported all.
712 return datastructures.SortedDict([
713 ('image', {
714 'type': _("Glance"),
715 'label': '',
716 'description': _("Image existence check"),
717 }),
718 ('image.size', {
719 'type': _("Glance"),
720 'label': '',
721 'description': _("Uploaded image size"),
722 }),
723 ('image.update', {
724 'type': _("Glance"),
725 'label': '',
726 'description': _("Number of image updates"),
727 }),
728 ('image.upload', {
729 'type': _("Glance"),
730 'label': '',
731 'description': _("Number of image uploads"),
732 }),
733 ('image.delete', {
734 'type': _("Glance"),
735 'label': '',
736 'description': _("Number of image deletions"),
737 }),
738 ('image.download', {
739 'type': _("Glance"),
740 'label': '',
741 'description': _("Image is downloaded"),
742 }),
743 ('image.serve', {
744 'type': _("Glance"),
745 'label': '',
746 'description': _("Image is served out"),
747 }),
748 ])
749
750 def _get_cinder_meters_info(self):
751 """Returns additional info for each meter.
752
753 That will be used for augmenting the Ceilometer meter.
754 """
755
756 # TODO(lsmola) Unless the Ceilometer will provide the information
757 # below, I need to define it as a static here. I will be joining this
758 # to info that I am able to obtain from Ceilometer meters, hopefully
759 # some day it will be supported all.
760 return datastructures.SortedDict([
761 ('volume', {
762 'type': _("Cinder"),
763 'label': '',
764 'description': _("Existence of volume"),
765 }),
766 ('volume.size', {
767 'type': _("Cinder"),
768 'label': '',
769 'description': _("Size of volume"),
770 }),
771 ])
772
773 def _get_swift_meters_info(self):
774 """Returns additional info for each meter.
775
776 That will be used for augmenting the Ceilometer meter.
777 """
778
779 # TODO(lsmola) Unless the Ceilometer will provide the information
780 # below, I need to define it as a static here. I will be joining this
781 # to info that I am able to obtain from Ceilometer meters, hopefully
782 # some day it will be supported all.
783 return datastructures.SortedDict([
784 ('storage.objects', {
785 'type': _("Swift"),
786 'label': '',
787 'description': _("Number of objects"),
788 }),
789 ('storage.objects.size', {
790 'type': _("Swift"),
791 'label': '',
792 'description': _("Total size of stored objects"),
793 }),
794 ('storage.objects.containers', {
795 'type': _("Swift"),
796 'label': '',
797 'description': _("Number of containers"),
798 }),
799 ('storage.objects.incoming.bytes', {
800 'type': _("Swift"),
801 'label': '',
802 'description': _("Number of incoming bytes"),
803 }),
804 ('storage.objects.outgoing.bytes', {
805 'type': _("Swift"),
806 'label': '',
807 'description': _("Number of outgoing bytes"),
808 }),
809 ('storage.api.request', {
810 'type': _("Swift"),
811 'label': '',
812 'description': _("Number of API requests against swift"),
813 }),
814 ])
815
816 def _get_kwapi_meters_info(self):
817 """Returns additional info for each meter.
818
819 That will be used for augmenting the Ceilometer meter.
820 """
821
822 # TODO(lsmola) Unless the Ceilometer will provide the information
823 # below, I need to define it as a static here. I will be joining this
824 # to info that I am able to obtain from Ceilometer meters, hopefully
825 # some day it will be supported all.
826 return datastructures.SortedDict([
827 ('energy', {
828 'type': _("Kwapi"),
829 'label': '',
830 'description': _("Amount of energy"),
831 }),
832 ('power', {
833 'type': _("Kwapi"),
834 'label': '',
835 'description': _("Power consumption"),
836 }),
837 ])
838
839 def _get_ipmi_meters_info(self):
840 """Returns additional info for each meter
841
842 That will be used for augmenting the Ceilometer meter
843 """
844
845 # TODO(lsmola) Unless the Ceilometer will provide the information
846 # below, I need to define it as a static here. I will be joining this
847 # to info that I am able to obtain from Ceilometer meters, hopefully
848 # some day it will be supported all.
849 return datastructures.SortedDict([
850 ('hardware.ipmi.node.power', {
851 'type': _("IPMI"),
852 'label': '',
853 'description': _("System Current Power"),
854 }),
855 ('hardware.ipmi.fan', {
856 'type': _("IPMI"),
857 'label': '',
858 'description': _("Fan RPM"),
859 }),
860 ('hardware.ipmi.temperature', {
861 'type': _("IPMI"),
862 'label': '',
863 'description': _("Sensor Temperature Reading"),
864 }),
865 ('hardware.ipmi.current', {
866 'type': _("IPMI"),
867 'label': '',
868 'description': _("Sensor Current Reading"),
869 }),
870 ('hardware.ipmi.voltage', {
871 'type': _("IPMI"),
872 'label': '',
873 'description': _("Sensor Voltage Reading"),
874 }),
875 ('hardware.ipmi.node.inlet_temperature', {
876 'type': _("IPMI"),
877 'label': '',
878 'description': _("System Inlet Temperature Reading"),
879 }),
880 ('hardware.ipmi.node.outlet_temperature', {
881 'type': _("IPMI"),
882 'label': '',
883 'description': _("System Outlet Temperature Reading"),
884 }),
885 ('hardware.ipmi.node.airflow', {
886 'type': _("IPMI"),
887 'label': '',
888 'description': _("System Airflow Reading"),
889 }),
890 ('hardware.ipmi.node.cups', {
891 'type': _("IPMI"),
892 'label': '',
893 'description': _("System CUPS Reading"),
894 }),
895 ('hardware.ipmi.node.cpu_util', {
896 'type': _("IPMI"),
897 'label': '',
898 'description': _("System CPU Utility Reading"),
899 }),
900 ('hardware.ipmi.node.mem_util', {
901 'type': _("IPMI"),
902 'label': '',
903 'description': _("System Memory Utility Reading"),
904 }),
905 ('hardware.ipmi.node.io_util', {
906 'type': _("IPMI"),
907 'label': '',
908 'description': _("System IO Utility Reading"),
909 }),
910 ])
911
912 def _get_vcpe_meters_info(self):
913 """Returns additional info for each meter
914
915 That will be used for augmenting the Ceilometer meter
916 """
917
918 # TODO(lsmola) Unless the Ceilometer will provide the information
919 # below, I need to define it as a static here. I will be joining this
920 # to info that I am able to obtain from Ceilometer meters, hopefully
921 # some day it will be supported all.
922 return datastructures.SortedDict([
923 ('vsg', {
924 'type': _("VSG"),
925 'label': '',
926 'description': _("Existence of vsg instance"),
927 }),
928 ('vsg.dns.cache.size', {
929 'type': _("VSG"),
930 'label': '',
931 'description': _("Number of entries in DNS cache"),
932 }),
933 ('vsg.dns.total_instered_entries', {
934 'type': _("VSG"),
935 'label': '',
936 'description': _("Total number of inserted entries into the cache"),
937 }),
938 ('vsg.dns.replaced_unexpired_entries', {
939 'type': _("VSG"),
940 'label': '',
941 'description': _("Unexpired entries that were thrown out of cache"),
942 }),
943 ('vsg.dns.queries_answered_locally', {
944 'type': _("VSG"),
945 'label': '',
946 'description': _("Number of cache hits"),
947 }),
948 ('vsg.dns.queries_forwarded', {
949 'type': _("VSG"),
950 'label': '',
951 'description': _("Number of cache misses"),
952 }),
953 ('vsg.dns.server.queries_sent', {
954 'type': _("VSG"),
955 'label': '',
956 'description': _("For each upstream server, the number of queries sent"),
957 }),
958 ('vsg.dns.server.queries_failed', {
959 'type': _("VSG"),
960 'label': '',
961 'description': _("For each upstream server, the number of queries failed"),
962 }),
963 ])
964
965 def _get_volt_meters_info(self):
966 """Returns additional info for each meter
967
968 That will be used for augmenting the Ceilometer meter
969 """
970
971 # TODO(lsmola) Unless the Ceilometer will provide the information
972 # below, I need to define it as a static here. I will be joining this
973 # to info that I am able to obtain from Ceilometer meters, hopefully
974 # some day it will be supported all.
975 return datastructures.SortedDict([
976 ('volt.device', {
977 'type': _("VOLT"),
978 'label': '',
979 'description': _("Existence of olt device"),
980 }),
981 ('volt.device.disconnect', {
982 'type': _("VOLT"),
983 'label': '',
984 'description': _("Olt device disconnected"),
985 }),
986 ('volt.device.subscriber', {
987 'type': _("VOLT"),
988 'label': '',
989 'description': _("Existence of olt subscriber"),
990 }),
991 ('volt.device.subscriber.unregister', {
992 'type': _("VOLT"),
993 'label': '',
994 'description': _("Olt subscriber unregistered"),
995 }),
996 ])
997
998 def _get_sdn_meters_info(self):
999 """Returns additional info for each meter
1000
1001 That will be used for augmenting the Ceilometer meter
1002 """
1003
1004 # TODO(lsmola) Unless the Ceilometer will provide the information
1005 # below, I need to define it as a static here. I will be joining this
1006 # to info that I am able to obtain from Ceilometer meters, hopefully
1007 # some day it will be supported all.
1008 return datastructures.SortedDict([
1009 ('switch', {
1010 'type': _("SDN"),
1011 'label': '',
1012 'description': _("Existence of switch"),
1013 }),
1014 ('switch.port', {
1015 'type': _("SDN"),
1016 'label': '',
1017 'description': _("Existence of port"),
1018 }),
1019 ('switch.port.receive.packets', {
1020 'type': _("SDN"),
1021 'label': '',
1022 'description': _("Packets received on port"),
1023 }),
1024 ('switch.port.transmit.packets', {
1025 'type': _("SDN"),
1026 'label': '',
1027 'description': _("Packets transmitted on port"),
1028 }),
1029 ('switch.port.receive.drops', {
1030 'type': _("SDN"),
1031 'label': '',
1032 'description': _("Drops received on port"),
1033 }),
1034 ('switch.port.transmit.drops', {
1035 'type': _("SDN"),
1036 'label': '',
1037 'description': _("Drops transmitted on port"),
1038 }),
1039 ('switch.port.receive.errors', {
1040 'type': _("SDN"),
1041 'label': '',
1042 'description': _("Errors received on port"),
1043 }),
1044 ('switch.port.transmit.errors', {
1045 'type': _("SDN"),
1046 'label': '',
1047 'description': _("Errors transmitted on port"),
1048 }),
1049 ('switch.flow', {
1050 'type': _("SDN"),
1051 'label': '',
1052 'description': _("Duration of flow"),
1053 }),
1054 ('switch.flow.packets', {
1055 'type': _("SDN"),
1056 'label': '',
1057 'description': _("Packets received"),
1058 }),
1059 ('switch.table', {
1060 'type': _("SDN"),
1061 'label': '',
1062 'description': _("Existence of table"),
1063 }),
1064 ('switch.table.active.entries', {
1065 'type': _("SDN"),
1066 'label': '',
1067 'description': _("Active entries in table"),
1068 }),
1069 ])
1070
1071def make_query(user_id=None, tenant_id=None, resource_id=None,
1072 user_ids=None, tenant_ids=None, resource_ids=None):
1073 """Returns query built from given parameters.
1074
1075 This query can be then used for querying resources, meters and
1076 statistics.
1077
1078 :Parameters:
1079 - `user_id`: user_id, has a priority over list of ids
1080 - `tenant_id`: tenant_id, has a priority over list of ids
1081 - `resource_id`: resource_id, has a priority over list of ids
1082 - `user_ids`: list of user_ids
1083 - `tenant_ids`: list of tenant_ids
1084 - `resource_ids`: list of resource_ids
1085 """
1086 user_ids = user_ids or []
1087 tenant_ids = tenant_ids or []
1088 resource_ids = resource_ids or []
1089
1090 query = []
1091 if user_id:
1092 user_ids = [user_id]
1093 for u_id in user_ids:
1094 query.append({"field": "user_id", "op": "eq", "value": u_id})
1095
1096 if tenant_id:
1097 tenant_ids = [tenant_id]
1098 for t_id in tenant_ids:
1099 query.append({"field": "project_id", "op": "eq", "value": t_id})
1100
1101 if resource_id:
1102 resource_ids = [resource_id]
1103 for r_id in resource_ids:
1104 query.append({"field": "resource_id", "op": "eq", "value": r_id})
1105
1106 return query
1107
1108def calc_date_args(date_from, date_to, date_options):
1109 # TODO(lsmola) all timestamps should probably work with
1110 # current timezone. And also show the current timezone in chart.
1111 if date_options == "other":
1112 try:
1113 if date_from:
1114 date_from = pytz.utc.localize(
1115 datetime.datetime.strptime(str(date_from), "%Y-%m-%d"))
1116 else:
1117 # TODO(lsmola) there should be probably the date
1118 # of the first sample as default, so it correctly
1119 # counts the time window. Though I need ordering
1120 # and limit of samples to obtain that.
1121 pass
1122 if date_to:
1123 date_to = pytz.utc.localize(
1124 datetime.datetime.strptime(str(date_to), "%Y-%m-%d"))
1125 # It returns the beginning of the day, I want the end of
1126 # the day, so I add one day without a second.
1127 date_to = (date_to + datetime.timedelta(days=1) -
1128 datetime.timedelta(seconds=1))
1129 else:
1130 date_to = timezone.now()
1131 except Exception:
1132 raise ValueError(_("The dates haven't been recognized"))
1133 else:
1134 try:
1135 date_to = timezone.now()
1136 date_from = date_to - datetime.timedelta(days=float(date_options))
1137 except Exception as e:
1138 raise e
1139 #raise ValueError(_("The time delta must be a number representing "
1140 # "the time span in days"))
1141 return date_from, date_to
1142
1143class MetersList(APIView):
1144 method_kind = "list"
1145 method_name = "meters"
1146
1147 def get(self, request, format=None):
1148 if (not request.user.is_authenticated()):
1149 raise PermissionDenied("You must be authenticated in order to use this API")
1150 tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
1151 if (not tenant_ceilometer_url):
1152 raise XOSMissingField("Tenant ceilometer URL is missing")
1153
1154 tenant_id = request.query_params.get('tenant', None)
1155 resource_id = request.query_params.get('resource', None)
1156
1157 query = []
1158 if tenant_id:
1159 query.extend(make_query(tenant_id=tenant_id))
1160 if resource_id:
1161 query.extend(make_query(resource_id=resource_id))
1162
1163 tenant_map = getTenantControllerTenantMap(request.user)
1164 resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
1165 meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
1166 services = {
1167 _('Nova'): meters.list_nova(),
1168 _('Neutron'): meters.list_neutron(),
1169 _('VSG'): meters.list_vcpe(),
1170 _('VOLT'): meters.list_volt(),
1171 _('SDN'): meters.list_sdn(),
1172 }
1173 meters = []
1174 for service,smeters in services.iteritems():
1175 meters.extend(smeters)
1176 return Response(meters)
1177
1178class MeterStatisticsList(APIView):
1179 method_kind = "list"
1180 method_name = "meterstatistics"
1181
1182 def get(self, request, format=None):
1183 if (not request.user.is_authenticated()):
1184 raise PermissionDenied("You must be authenticated in order to use this API")
1185 tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
1186 if (not tenant_ceilometer_url):
1187 raise XOSMissingField("Tenant ceilometer URL is missing")
1188 tenant_map = getTenantControllerTenantMap(request.user)
1189
1190 date_options = request.query_params.get('period', 1)
1191 date_from = request.query_params.get('date_from', '')
1192 date_to = request.query_params.get('date_to', '')
1193
1194 try:
1195 date_from, date_to = calc_date_args(date_from,
1196 date_to,
1197 date_options)
1198 except Exception as e:
1199 raise e
1200
1201 additional_query = []
1202 if date_from:
1203 additional_query.append({'field': 'timestamp',
1204 'op': 'ge',
1205 'value': date_from})
1206 if date_to:
1207 additional_query.append({'field': 'timestamp',
1208 'op': 'le',
1209 'value': date_to})
1210
1211 meter_name = request.query_params.get('meter', None)
1212 tenant_id = request.query_params.get('tenant', None)
1213 resource_id = request.query_params.get('resource', None)
1214
1215 query = []
1216 if tenant_id:
1217 query.extend(make_query(tenant_id=tenant_id))
1218 if resource_id:
1219 query.extend(make_query(resource_id=resource_id))
1220
1221 if meter_name:
1222 #Statistics query for one meter
1223 if additional_query:
1224 query = query + additional_query
1225 statistics = statistic_list(request, meter_name,
1226 ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
1227 statistic = statistics[-1]
1228 row = {"name": 'none',
1229 "meter": meter_name,
1230 "time": statistic["period_end"],
1231 "value": statistic["avg"]}
1232 return Response(row)
1233
1234 #Statistics query for all meter
1235 resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
1236 meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
1237 services = {
1238 _('Nova'): meters.list_nova(),
1239 _('Neutron'): meters.list_neutron(),
1240 _('VSG'): meters.list_vcpe(),
1241 _('VOLT'): meters.list_volt(),
1242 _('SDN'): meters.list_sdn(),
1243 }
1244 report_rows = []
1245 for service,meters in services.items():
1246 for meter in meters:
1247 query = make_query(tenant_id=meter["project_id"],resource_id=meter["resource_id"])
1248 if additional_query:
1249 query = query + additional_query
1250 try:
1251 statistics = statistic_list(request, meter["name"],
1252 ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
1253 except Exception as e:
1254 logger.error('Exception during statistics query for meter %(meter)s and reason:%(reason)s' % {'meter':meter["name"], 'reason':str(e)})
1255 statistics = None
1256
1257 if not statistics:
1258 continue
1259 statistic = statistics[-1]
1260 row = {"name": 'none',
1261 "slice": meter["slice"],
1262 "project_id": meter["project_id"],
1263 "service": meter["service"],
1264 "resource_id": meter["resource_id"],
1265 "resource_name": meter["resource_name"],
1266 "meter": meter["name"],
1267 "description": meter["description"],
1268 "category": service,
1269 "time": statistic["period_end"],
1270 "value": statistic["avg"],
1271 "unit": meter["unit"]}
1272 report_rows.append(row)
1273
1274 return Response(report_rows)
1275
1276
1277class MeterSamplesList(APIView):
1278 method_kind = "list"
1279 method_name = "metersamples"
1280
1281 def get(self, request, format=None):
1282 if (not request.user.is_authenticated()):
1283 raise PermissionDenied("You must be authenticated in order to use this API")
1284 tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
1285 if (not tenant_ceilometer_url):
1286 raise XOSMissingField("Tenant ceilometer URL is missing")
1287 meter_name = request.query_params.get('meter', None)
1288 if not meter_name:
1289 raise XOSMissingField("Meter name in query params is missing")
1290 limit = request.query_params.get('limit', 10)
1291 tenant_id = request.query_params.get('tenant', None)
1292 resource_id = request.query_params.get('resource', None)
1293 query = []
1294 if tenant_id:
1295 query.extend(make_query(tenant_id=tenant_id))
1296 if resource_id:
1297 query.extend(make_query(resource_id=resource_id))
1298 query.append({"field": "meter", "op": "eq", "value": meter_name})
1299 samples = sample_list(request, meter_name,
1300 ceilometer_url=tenant_ceilometer_url, query=query, limit=limit)
1301 if samples:
1302 tenant_map = getTenantControllerTenantMap(request.user)
1303 resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url)
1304 for sample in samples:
1305 if sample["project_id"] in tenant_map.keys():
1306 sample["slice"] = tenant_map[sample["project_id"]]["slice"]
1307 else:
1308 sample["slice"] = sample["project_id"]
1309 if sample["resource_id"] in resource_map.keys():
1310 sample["resource_name"] = resource_map[sample["resource_id"]]
1311 else:
1312 sample["resource_name"] = sample["resource_id"]
1313 return Response(samples)
1314
1315class XOSSliceServiceList(APIView):
1316 method_kind = "list"
1317 method_name = "xos-slice-service-mapping"
1318
1319 def get(self, request, format=None):
1320 if (not request.user.is_authenticated()):
1321 raise PermissionDenied("You must be authenticated in order to use this API")
1322 tenant_map = getTenantControllerTenantMap(request.user)
1323 service_map={}
1324 for k,v in tenant_map.iteritems():
1325 if not (v['service'] in service_map.keys()):
1326 service_map[v['service']] = {}
1327 service_map[v['service']]['service'] = v['service']
1328 service_map[v['service']]['slices'] = []
1329 slice_details = {'slice':v['slice'], 'project_id':k}
1330 service_map[v['service']]['slices'].append(slice_details)
1331 return Response(service_map.values())
1332
1333class XOSInstanceStatisticsList(APIView):
1334 method_kind = "list"
1335 method_name = "xos-instance-statistics"
1336
1337 def get(self, request, format=None):
1338 if (not request.user.is_authenticated()):
1339 raise PermissionDenied("You must be authenticated in order to use this API")
1340 tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
1341 if (not tenant_ceilometer_url):
1342 raise XOSMissingField("Tenant ceilometer URL is missing")
1343 instance_uuid = request.query_params.get('instance-uuid', None)
1344 if not instance_uuid:
1345 raise XOSMissingField("Instance UUID in query params is missing")
1346 if not Instance.objects.filter(instance_uuid=instance_uuid):
1347 raise XOSMissingField("XOS Instance object is missing for this uuid")
1348 xos_instance = Instance.objects.filter(instance_uuid=instance_uuid)[0]
1349 tenant_map = getTenantControllerTenantMap(request.user, xos_instance.slice)
1350 tenant_id = tenant_map.keys()[0]
1351 resource_ids = []
1352 resource_ids.append(instance_uuid)
1353 for p in xos_instance.ports.all():
1354 #neutron port resource id is represented in ceilometer as "nova instance-name"+"-"+"nova instance-id"+"-"+"tap"+first 11 characters of port-id
1355 resource_ids.append(xos_instance.instance_id+"-"+instance_uuid+"-tap"+p.port_id[:11])
1356
1357 date_options = request.query_params.get('period', 1)
1358 date_from = request.query_params.get('date_from', '')
1359 date_to = request.query_params.get('date_to', '')
1360
1361 try:
1362 date_from, date_to = calc_date_args(date_from,
1363 date_to,
1364 date_options)
1365 except Exception as e:
1366 raise e
1367
1368 additional_query = []
1369 if date_from:
1370 additional_query.append({'field': 'timestamp',
1371 'op': 'ge',
1372 'value': date_from})
1373 if date_to:
1374 additional_query.append({'field': 'timestamp',
1375 'op': 'le',
1376 'value': date_to})
1377
1378 report_rows = []
1379 for resource_id in resource_ids:
1380 query = []
1381 if tenant_id:
1382 query.extend(make_query(tenant_id=tenant_id))
1383 if resource_id:
1384 query.extend(make_query(resource_id=resource_id))
1385
1386 #Statistics query for all meter
1387 resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
1388 meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
1389 exclude_nova_meters_info = [ "instance", "instance:<type>", "disk.read.requests", "disk.write.requests",
1390 "disk.read.bytes", "disk.write.bytes", "disk.read.requests.rate", "disk.write.requests.rate", "disk.read.bytes.rate",
1391 "disk.write.bytes.rate", "disk.root.size", "disk.ephemeral.size"]
1392 exclude_neutron_meters_info = [ 'network.create', 'network.update', 'subnet.create',
1393 'subnet.update', 'port.create', 'port.update', 'router.create', 'router.update',
1394 'ip.floating.create', 'ip.floating.update']
1395 services = {
1396 _('Nova'): meters.list_nova(except_meters=exclude_nova_meters_info),
1397 _('Neutron'): meters.list_neutron(except_meters=exclude_neutron_meters_info),
1398 _('VSG'): meters.list_vcpe(),
1399 _('VOLT'): meters.list_volt(),
1400 _('SDN'): meters.list_sdn(),
1401 }
1402 for service,meters in services.items():
1403 for meter in meters:
1404 query = make_query(tenant_id=meter["project_id"],resource_id=meter["resource_id"])
1405 if additional_query:
1406 query = query + additional_query
1407 try:
1408 statistics = statistic_list(request, meter["name"],
1409 ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
1410 except Exception as e:
1411 logger.error('Exception during statistics query for meter %(meter)s and reason:%(reason)s' % {'meter':meter["name"], 'reason':str(e)})
1412 statistics = None
1413
1414 if not statistics:
1415 continue
1416 statistic = statistics[-1]
1417 row = {"name": 'none',
1418 "slice": meter["slice"],
1419 "project_id": meter["project_id"],
1420 "service": meter["service"],
1421 "resource_id": meter["resource_id"],
1422 "resource_name": meter["resource_name"],
1423 "meter": meter["name"],
1424 "description": meter["description"],
1425 "category": service,
1426 "time": statistic["period_end"],
1427 "value": statistic["avg"],
1428 "unit": meter["unit"]}
1429 report_rows.append(row)
1430
1431 return Response(report_rows)
1432
1433class ServiceAdjustScale(APIView):
1434 method_kind = "list"
1435 method_name = "serviceadjustscale"
1436
1437 def get(self, request, format=None):
1438 if (not request.user.is_authenticated()) or (not request.user.is_admin):
1439 raise PermissionDenied("You must be authenticated admin user in order to use this API")
1440 service = request.query_params.get('service', None)
1441 slice_hint = request.query_params.get('slice_hint', None)
1442 scale = request.query_params.get('scale', None)
1443 if not service or not slice_hint or not scale:
1444 raise XOSMissingField("Mandatory fields missing")
1445 services = Service.select_by_user(request.user)
1446 logger.info('SRIKANTH: Services for this user %(services)s' % {'services':services})
1447 if not services or (not services.get(name=service)):
1448 raise XOSMissingField("Service not found")
1449 service = services.get(name=service)
1450 service.adjust_scale(slice_hint, int(scale))
1451 return Response("Success")