blob: 22b21f8f3acb11376f422329d564ca034b8a6582 [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import requests
from six.moves import urllib
import urllib2
import pytz
import datetime
import time
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import serializers
from rest_framework import generics
from rest_framework.views import APIView
from core.models import *
from services.monitoring.models import MonitoringChannel, CeilometerService
from django.forms import widgets
from django.utils import datastructures
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.core.exceptions import PermissionDenied
from xos.logger import observer_logger as logger
# This REST API endpoint provides information that the ceilometer view needs to display
def getTenantCeilometerProxyURL(user):
monitoring_channel = None
for obj in MonitoringChannel.get_tenant_objects().all():
if (obj.creator.username == user.username):
monitoring_channel = obj
break
if not monitoring_channel:
raise XOSMissingField("Monitoring channel is missing for this tenant...Create one and invoke this REST API")
#TODO: Wait until URL is completely UP
MAX_ATTEMPTS = 5
attempts = 0
while True:
try:
response = urllib2.urlopen(monitoring_channel.ceilometer_ssh_proxy_url)
break
except urllib2.HTTPError, e:
logger.info('HTTP error %(reason)s' % {'reason':e.reason})
break
except urllib2.URLError, e:
attempts += 1
if attempts >= MAX_ATTEMPTS:
raise XOSServiceUnavailable("Ceilometer channel is not ready yet...Try again later")
logger.info('URL error %(reason)s' % {'reason':e.reason})
time.sleep(1)
pass
logger.info("Ceilometer proxy URL for user %(user)s is %(url)s" % {'user':user.username,'url':monitoring_channel.ceilometer_ssh_proxy_url})
return monitoring_channel.ceilometer_ssh_proxy_url
def getTenantControllerTenantMap(user, slice=None):
tenantmap={}
if not slice:
slices = Slice.objects.filter(creator=user)
else:
slices = [slice]
for s in slices:
for cs in s.controllerslices.all():
if cs.tenant_id:
tenantmap[cs.tenant_id] = {"slice": cs.slice.name}
if cs.slice.service:
tenantmap[cs.tenant_id]["service"] = cs.slice.service.name
else:
logger.warn("SRIKANTH: Slice %(slice)s is not associated with any service" % {'slice':cs.slice.name})
tenantmap[cs.tenant_id]["service"] = "Other"
if not slice:
#TEMPORARY WORK AROUND: There are some resource in network like whitebox switches does not belong to a specific tenant.
#They are all associated with "default_admin_tenant" tenant
tenantmap["default_admin_tenant"] = {"slice": "default_admin_tenant", "service": "NetworkInfra"}
return tenantmap
def build_url(path, q, params=None):
"""Convert list of dicts and a list of params to query url format.
This will convert the following:
"[{field=this,op=le,value=34},
{field=that,op=eq,value=foo,type=string}],
['foo=bar','sna=fu']"
to:
"?q.field=this&q.field=that&
q.op=le&q.op=eq&
q.type=&q.type=string&
q.value=34&q.value=foo&
foo=bar&sna=fu"
"""
if q:
query_params = {'q.field': [],
'q.value': [],
'q.op': [],
'q.type': []}
for query in q:
for name in ['field', 'op', 'value', 'type']:
query_params['q.%s' % name].append(query.get(name, ''))
# Transform the dict to a sequence of two-element tuples in fixed
# order, then the encoded string will be consistent in Python 2&3.
new_qparams = sorted(query_params.items(), key=lambda x: x[0])
path += "?" + urllib.parse.urlencode(new_qparams, doseq=True)
if params:
for p in params:
path += '&%s' % p
elif params:
path += '?%s' % params[0]
for p in params[1:]:
path += '&%s' % p
return path
def concat_url(endpoint, url):
"""Concatenate endpoint and final URL.
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
"http://keystone/v2.0/tokens".
:param endpoint: the base URL
:param url: the final URL
"""
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
def resource_list(request, query=None, ceilometer_url=None, ceilometer_usage_object=None):
"""List the resources."""
url = concat_url(ceilometer_url, build_url('/v2/resources', query))
try:
response = requests.get(url)
except requests.exceptions.RequestException as e:
raise e
return response.json()
def sample_list(request, meter_name, ceilometer_url=None, query=None, limit=None):
"""List the samples for this meters."""
params = ['limit=%s' % limit] if limit else []
url = concat_url(ceilometer_url, build_url('/v2/samples', query, params))
try:
response = requests.get(url)
except requests.exceptions.RequestException as e:
raise e
return response.json()
def meter_list(request, ceilometer_url=None, query=None):
"""List the user's meters."""
url = concat_url(ceilometer_url, build_url('/v2/meters', query))
try:
response = requests.get(url)
except requests.exceptions.RequestException as e:
raise e
return response.json()
def statistic_list(request, meter_name, ceilometer_url=None, query=None, period=None):
"""List of statistics."""
p = ['period=%s' % period] if period else []
url = concat_url(ceilometer_url, build_url('/v2/meters/' + meter_name + '/statistics', query, p))
try:
response = requests.get(url)
except requests.exceptions.RequestException as e:
raise e
return response.json()
def diff_lists(a, b):
if not a:
return []
elif not b:
return a
else:
return list(set(a) - set(b))
def get_resource_map(request, ceilometer_url, query=None):
resource_map = {}
try:
resources = resource_list(request, ceilometer_url=ceilometer_url, query=query)
for r in resources:
if 'display_name' in r['metadata']:
name = r['metadata']['display_name']
elif 'name' in r['metadata']:
name = r['metadata']['name']
#Output of 'resources' REST query has chnaged from kilo to mitaka,below if conditions to handle mitaka output
elif 'resource_metadata.display_name' in r['metadata']:
name = r['metadata']['resource_metadata.display_name']
elif 'resource_metadata.name' in r['metadata']:
name = r['metadata']['resource_metadata.name']
else:
name = r['resource_id']
resource_map[r['resource_id']] = name
except requests.exceptions.RequestException as e:
raise e
return resource_map
class Meters(object):
"""Class for listing of available meters.
It is listing meters defined in this class that are available
in Ceilometer meter_list.
It is storing information that is not available in Ceilometer, i.e.
label, description.
"""
def __init__(self, request=None, ceilometer_meter_list=None, ceilometer_url=None, query=None, tenant_map=None, resource_map=None):
# Storing the request.
self._request = request
self.ceilometer_url = ceilometer_url
self.tenant_map = tenant_map
self.resource_map = resource_map
# Storing the Ceilometer meter list
if ceilometer_meter_list:
self._ceilometer_meter_list = ceilometer_meter_list
else:
try:
meter_query=[]
if query:
meter_query = query
self._ceilometer_meter_list = meter_list(request, self.ceilometer_url, meter_query)
except requests.exceptions.RequestException as e:
self._ceilometer_meter_list = []
raise e
# Storing the meters info categorized by their services.
self._nova_meters_info = self._get_nova_meters_info()
self._neutron_meters_info = self._get_neutron_meters_info()
self._glance_meters_info = self._get_glance_meters_info()
self._cinder_meters_info = self._get_cinder_meters_info()
self._swift_meters_info = self._get_swift_meters_info()
self._kwapi_meters_info = self._get_kwapi_meters_info()
self._ipmi_meters_info = self._get_ipmi_meters_info()
self._vcpe_meters_info = self._get_vcpe_meters_info()
self._volt_meters_info = self._get_volt_meters_info()
self._sdn_meters_info = self._get_sdn_meters_info()
self._broadview_meters_info = self._get_broadview_meters_info()
# Storing the meters info of all services together.
all_services_meters = (self._nova_meters_info,
self._neutron_meters_info,
self._glance_meters_info,
self._cinder_meters_info,
self._swift_meters_info,
self._kwapi_meters_info,
self._ipmi_meters_info,
self._vcpe_meters_info,
self._volt_meters_info,
self._sdn_meters_info,
self._broadview_meters_info)
self._all_meters_info = {}
for service_meters in all_services_meters:
self._all_meters_info.update(dict([(meter_name, meter_info)
for meter_name, meter_info
in service_meters.items()]))
# Here will be the cached Meter objects, that will be reused for
# repeated listing.
self._cached_meters = {}
self._cached_all_meters = []
def get_all(self):
if not self._cached_all_meters:
for meter in self._ceilometer_meter_list:
meter_info = self._all_meters_info.get(meter['name'], None)
if meter_info:
label = meter_info["label"]
description = meter_info["description"]
meter_category = meter_info["type"]
else:
label = ""
description = ""
meter_category = "Other"
meter["label"] = label
meter["description"] = description
meter["category"] = meter_category
if meter["project_id"] in self.tenant_map.keys():
meter["slice"] = self.tenant_map[meter["project_id"]]["slice"]
meter["service"] = self.tenant_map[meter["project_id"]]["service"]
else:
meter["slice"] = meter["project_id"]
meter["service"] = "Other"
if meter["resource_id"] in self.resource_map.keys():
meter["resource_name"] = self.resource_map[meter["resource_id"]]
self._cached_all_meters.append(meter)
return self._cached_all_meters
def list_all(self, only_meters=None, except_meters=None):
"""Returns a list of meters based on the meters names.
:Parameters:
- `only_meters`: The list of meter names we want to show.
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=only_meters,
except_meters=except_meters)
def list_nova(self, except_meters=None):
"""Returns a list of meters tied to nova.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._nova_meters_info.keys(),
except_meters=except_meters)
def list_neutron(self, except_meters=None):
"""Returns a list of meters tied to neutron.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._neutron_meters_info.keys(),
except_meters=except_meters)
def list_glance(self, except_meters=None):
"""Returns a list of meters tied to glance.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._glance_meters_info.keys(),
except_meters=except_meters)
def list_cinder(self, except_meters=None):
"""Returns a list of meters tied to cinder.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._cinder_meters_info.keys(),
except_meters=except_meters)
def list_swift(self, except_meters=None):
"""Returns a list of meters tied to swift.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._swift_meters_info.keys(),
except_meters=except_meters)
def list_kwapi(self, except_meters=None):
"""Returns a list of meters tied to kwapi.
:Parameters:
- `except_meters`: The list of meter names we don't want to show.
"""
return self._list(only_meters=self._kwapi_meters_info.keys(),
except_meters=except_meters)
def list_ipmi(self, except_meters=None):
"""Returns a list of meters tied to ipmi
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
return self._list(only_meters=self._ipmi_meters_info.keys(),
except_meters=except_meters)
def list_vcpe(self, except_meters=None):
"""Returns a list of meters tied to vcpe service
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
return self._list(only_meters=self._vcpe_meters_info.keys(),
except_meters=except_meters)
def list_volt(self, except_meters=None):
"""Returns a list of meters tied to volt service
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
return self._list(only_meters=self._volt_meters_info.keys(),
except_meters=except_meters)
def list_sdn(self, except_meters=None):
"""Returns a list of meters tied to sdn service
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
return self._list(only_meters=self._sdn_meters_info.keys(),
except_meters=except_meters)
def list_broadview(self, except_meters=None):
"""Returns a list of meters tied to broadview service
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
return self._list(only_meters=self._broadview_meters_info.keys(),
except_meters=except_meters)
def list_other_services(self, except_meters=None):
"""Returns a list of meters tied to ipmi
:Parameters:
- `except_meters`: The list of meter names we don't want to show
"""
other_service_meters = [m for m in self._ceilometer_meter_list
if m.name not in self._all_meters_info.keys()]
other_service_meters = diff_lists(other_service_meters, except_meters)
meters = []
for meter in other_service_meters:
self._cached_meters[meter.name] = meter
meters.append(meter)
return meters
def _list(self, only_meters=None, except_meters=None):
"""Returns a list of meters based on the meters names.
:Parameters:
- `only_meters`: The list of meter names we want to show.
- `except_meters`: The list of meter names we don't want to show.
"""
# Get all wanted meter names.
if only_meters:
meter_names = only_meters
else:
meter_names = [meter_name for meter_name
in self._all_meters_info.keys()]
meter_names = diff_lists(meter_names, except_meters)
# Collect meters for wanted meter names.
return self._get_meters(meter_names)
def _get_meters(self, meter_names):
"""Obtain meters based on meter_names.
The meters that do not exist in Ceilometer meter list are left out.
:Parameters:
- `meter_names`: A list of meter names we want to fetch.
"""
meters = []
for meter_name in meter_names:
meter_candidates = self._get_meter(meter_name)
if meter_candidates:
meters.extend(meter_candidates)
return meters
def _get_meter(self, meter_name):
"""Obtains a meter.
Obtains meter either from cache or from Ceilometer meter list
joined with statically defined meter info like label and description.
:Parameters:
- `meter_name`: A meter name we want to fetch.
"""
meter_candidates = self._cached_meters.get(meter_name, None)
if not meter_candidates:
meter_candidates = [m for m in self._ceilometer_meter_list
if m["name"] == meter_name]
if meter_candidates:
meter_info = self._all_meters_info.get(meter_name, None)
if meter_info:
label = meter_info["label"]
description = meter_info["description"]
meter_category = meter_info["type"]
else:
label = ""
description = ""
meter_category = "Other"
for meter in meter_candidates:
meter["label"] = label
meter["description"] = description
meter["category"] = meter_category
if meter["project_id"] in self.tenant_map.keys():
meter["slice"] = self.tenant_map[meter["project_id"]]["slice"]
meter["service"] = self.tenant_map[meter["project_id"]]["service"]
else:
meter["slice"] = meter["project_id"]
meter["service"] = "Other"
if meter["resource_id"] in self.resource_map.keys():
meter["resource_name"] = self.resource_map[meter["resource_id"]]
self._cached_meters[meter_name] = meter_candidates
return meter_candidates
def _get_nova_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
meters_info = datastructures.SortedDict([
("instance", {
'type': _("Nova"),
'label': '',
'description': _("Existence of instance"),
}),
("instance:<type>", {
'type': _("Nova"),
'label': '',
'description': _("Existence of instance <type> "
"(openstack types)"),
}),
("memory", {
'type': _("Nova"),
'label': '',
'description': _("Volume of RAM"),
}),
("memory.usage", {
'type': _("Nova"),
'label': '',
'description': _("Volume of RAM used"),
}),
("cpu", {
'type': _("Nova"),
'label': '',
'description': _("CPU time used"),
}),
("cpu_util", {
'type': _("Nova"),
'label': '',
'description': _("Average CPU utilization"),
}),
("vcpus", {
'type': _("Nova"),
'label': '',
'description': _("Number of VCPUs"),
}),
("disk.read.requests", {
'type': _("Nova"),
'label': '',
'description': _("Number of read requests"),
}),
("disk.write.requests", {
'type': _("Nova"),
'label': '',
'description': _("Number of write requests"),
}),
("disk.read.bytes", {
'type': _("Nova"),
'label': '',
'description': _("Volume of reads"),
}),
("disk.write.bytes", {
'type': _("Nova"),
'label': '',
'description': _("Volume of writes"),
}),
("disk.read.requests.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate of read requests"),
}),
("disk.write.requests.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate of write requests"),
}),
("disk.read.bytes.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate of reads"),
}),
("disk.write.bytes.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average volume of writes"),
}),
("disk.root.size", {
'type': _("Nova"),
'label': '',
'description': _("Size of root disk"),
}),
("disk.ephemeral.size", {
'type': _("Nova"),
'label': '',
'description': _("Size of ephemeral disk"),
}),
("network.incoming.bytes", {
'type': _("Nova"),
'label': '',
'description': _("Number of incoming bytes "
"on the network for a VM interface"),
}),
("network.outgoing.bytes", {
'type': _("Nova"),
'label': '',
'description': _("Number of outgoing bytes "
"on the network for a VM interface"),
}),
("network.incoming.packets", {
'type': _("Nova"),
'label': '',
'description': _("Number of incoming "
"packets for a VM interface"),
}),
("network.outgoing.packets", {
'type': _("Nova"),
'label': '',
'description': _("Number of outgoing "
"packets for a VM interface"),
}),
("network.incoming.bytes.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of incoming "
"bytes on a VM network interface"),
}),
("network.outgoing.bytes.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of outgoing "
"bytes on a VM network interface"),
}),
("network.incoming.packets.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of incoming "
"packets on a VM network interface"),
}),
("network.outgoing.packets.rate", {
'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of outgoing "
"packets on a VM network interface"),
}),
])
# Adding flavor based meters into meters_info dict
# TODO(lsmola) this kind of meter will be probably deprecated
# https://bugs.launchpad.net/ceilometer/+bug/1208365 . Delete it then.
#for flavor in get_flavor_names(self._request):
# name = 'instance:%s' % flavor
# meters_info[name] = dict(meters_info["instance:<type>"])
# meters_info[name]['description'] = (
# _('Duration of instance type %s (openstack flavor)') %
# flavor)
# TODO(lsmola) allow to set specific in local_settings. For all meters
# because users can have their own agents and meters.
return meters_info
def _get_neutron_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('network', {
'type': _("Neutron"),
'label': '',
'description': _("Existence of network"),
}),
('network.create', {
'type': _("Neutron"),
'label': '',
'description': _("Creation requests for this network"),
}),
('network.update', {
'type': _("Neutron"),
'label': '',
'description': _("Update requests for this network"),
}),
('subnet', {
'type': _("Neutron"),
'label': '',
'description': _("Existence of subnet"),
}),
('subnet.create', {
'type': _("Neutron"),
'label': '',
'description': _("Creation requests for this subnet"),
}),
('subnet.update', {
'type': _("Neutron"),
'label': '',
'description': _("Update requests for this subnet"),
}),
('port', {
'type': _("Neutron"),
'label': '',
'description': _("Existence of port"),
}),
('port.create', {
'type': _("Neutron"),
'label': '',
'description': _("Creation requests for this port"),
}),
('port.update', {
'type': _("Neutron"),
'label': '',
'description': _("Update requests for this port"),
}),
('router', {
'type': _("Neutron"),
'label': '',
'description': _("Existence of router"),
}),
('router.create', {
'type': _("Neutron"),
'label': '',
'description': _("Creation requests for this router"),
}),
('router.update', {
'type': _("Neutron"),
'label': '',
'description': _("Update requests for this router"),
}),
('ip.floating', {
'type': _("Neutron"),
'label': '',
'description': _("Existence of floating ip"),
}),
('ip.floating.create', {
'type': _("Neutron"),
'label': '',
'description': _("Creation requests for this floating ip"),
}),
('ip.floating.update', {
'type': _("Neutron"),
'label': '',
'description': _("Update requests for this floating ip"),
}),
])
def _get_glance_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('image', {
'type': _("Glance"),
'label': '',
'description': _("Image existence check"),
}),
('image.size', {
'type': _("Glance"),
'label': '',
'description': _("Uploaded image size"),
}),
('image.update', {
'type': _("Glance"),
'label': '',
'description': _("Number of image updates"),
}),
('image.upload', {
'type': _("Glance"),
'label': '',
'description': _("Number of image uploads"),
}),
('image.delete', {
'type': _("Glance"),
'label': '',
'description': _("Number of image deletions"),
}),
('image.download', {
'type': _("Glance"),
'label': '',
'description': _("Image is downloaded"),
}),
('image.serve', {
'type': _("Glance"),
'label': '',
'description': _("Image is served out"),
}),
])
def _get_cinder_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('volume', {
'type': _("Cinder"),
'label': '',
'description': _("Existence of volume"),
}),
('volume.size', {
'type': _("Cinder"),
'label': '',
'description': _("Size of volume"),
}),
])
def _get_swift_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('storage.objects', {
'type': _("Swift"),
'label': '',
'description': _("Number of objects"),
}),
('storage.objects.size', {
'type': _("Swift"),
'label': '',
'description': _("Total size of stored objects"),
}),
('storage.objects.containers', {
'type': _("Swift"),
'label': '',
'description': _("Number of containers"),
}),
('storage.objects.incoming.bytes', {
'type': _("Swift"),
'label': '',
'description': _("Number of incoming bytes"),
}),
('storage.objects.outgoing.bytes', {
'type': _("Swift"),
'label': '',
'description': _("Number of outgoing bytes"),
}),
('storage.api.request', {
'type': _("Swift"),
'label': '',
'description': _("Number of API requests against swift"),
}),
])
def _get_kwapi_meters_info(self):
"""Returns additional info for each meter.
That will be used for augmenting the Ceilometer meter.
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('energy', {
'type': _("Kwapi"),
'label': '',
'description': _("Amount of energy"),
}),
('power', {
'type': _("Kwapi"),
'label': '',
'description': _("Power consumption"),
}),
])
def _get_ipmi_meters_info(self):
"""Returns additional info for each meter
That will be used for augmenting the Ceilometer meter
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('hardware.ipmi.node.power', {
'type': _("IPMI"),
'label': '',
'description': _("System Current Power"),
}),
('hardware.ipmi.fan', {
'type': _("IPMI"),
'label': '',
'description': _("Fan RPM"),
}),
('hardware.ipmi.temperature', {
'type': _("IPMI"),
'label': '',
'description': _("Sensor Temperature Reading"),
}),
('hardware.ipmi.current', {
'type': _("IPMI"),
'label': '',
'description': _("Sensor Current Reading"),
}),
('hardware.ipmi.voltage', {
'type': _("IPMI"),
'label': '',
'description': _("Sensor Voltage Reading"),
}),
('hardware.ipmi.node.inlet_temperature', {
'type': _("IPMI"),
'label': '',
'description': _("System Inlet Temperature Reading"),
}),
('hardware.ipmi.node.outlet_temperature', {
'type': _("IPMI"),
'label': '',
'description': _("System Outlet Temperature Reading"),
}),
('hardware.ipmi.node.airflow', {
'type': _("IPMI"),
'label': '',
'description': _("System Airflow Reading"),
}),
('hardware.ipmi.node.cups', {
'type': _("IPMI"),
'label': '',
'description': _("System CUPS Reading"),
}),
('hardware.ipmi.node.cpu_util', {
'type': _("IPMI"),
'label': '',
'description': _("System CPU Utility Reading"),
}),
('hardware.ipmi.node.mem_util', {
'type': _("IPMI"),
'label': '',
'description': _("System Memory Utility Reading"),
}),
('hardware.ipmi.node.io_util', {
'type': _("IPMI"),
'label': '',
'description': _("System IO Utility Reading"),
}),
])
def _get_vcpe_meters_info(self):
"""Returns additional info for each meter
That will be used for augmenting the Ceilometer meter
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('vsg', {
'type': _("VSG"),
'label': '',
'description': _("Existence of vsg instance"),
}),
('vsg.dns.cache.size', {
'type': _("VSG"),
'label': '',
'description': _("Number of entries in DNS cache"),
}),
('vsg.dns.total_instered_entries', {
'type': _("VSG"),
'label': '',
'description': _("Total number of inserted entries into the cache"),
}),
('vsg.dns.replaced_unexpired_entries', {
'type': _("VSG"),
'label': '',
'description': _("Unexpired entries that were thrown out of cache"),
}),
('vsg.dns.queries_answered_locally', {
'type': _("VSG"),
'label': '',
'description': _("Number of cache hits"),
}),
('vsg.dns.queries_forwarded', {
'type': _("VSG"),
'label': '',
'description': _("Number of cache misses"),
}),
('vsg.dns.server.queries_sent', {
'type': _("VSG"),
'label': '',
'description': _("For each upstream server, the number of queries sent"),
}),
('vsg.dns.server.queries_failed', {
'type': _("VSG"),
'label': '',
'description': _("For each upstream server, the number of queries failed"),
}),
])
def _get_volt_meters_info(self):
"""Returns additional info for each meter
That will be used for augmenting the Ceilometer meter
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('volt.device', {
'type': _("VOLT"),
'label': '',
'description': _("Existence of olt device"),
}),
('volt.device.disconnect', {
'type': _("VOLT"),
'label': '',
'description': _("Olt device disconnected"),
}),
('volt.device.subscriber', {
'type': _("VOLT"),
'label': '',
'description': _("Existence of olt subscriber"),
}),
('volt.device.subscriber.unregister', {
'type': _("VOLT"),
'label': '',
'description': _("Olt subscriber unregistered"),
}),
])
def _get_sdn_meters_info(self):
"""Returns additional info for each meter
That will be used for augmenting the Ceilometer meter
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('switch', {
'type': _("SDN"),
'label': '',
'description': _("Existence of switch"),
}),
('switch.port', {
'type': _("SDN"),
'label': '',
'description': _("Existence of port"),
}),
('switch.port.receive.packets', {
'type': _("SDN"),
'label': '',
'description': _("Packets received on port"),
}),
('switch.port.transmit.packets', {
'type': _("SDN"),
'label': '',
'description': _("Packets transmitted on port"),
}),
('switch.port.receive.drops', {
'type': _("SDN"),
'label': '',
'description': _("Drops received on port"),
}),
('switch.port.transmit.drops', {
'type': _("SDN"),
'label': '',
'description': _("Drops transmitted on port"),
}),
('switch.port.receive.errors', {
'type': _("SDN"),
'label': '',
'description': _("Errors received on port"),
}),
('switch.port.transmit.errors', {
'type': _("SDN"),
'label': '',
'description': _("Errors transmitted on port"),
}),
('switch.flow', {
'type': _("SDN"),
'label': '',
'description': _("Duration of flow"),
}),
('switch.flow.packets', {
'type': _("SDN"),
'label': '',
'description': _("Packets received"),
}),
('switch.table', {
'type': _("SDN"),
'label': '',
'description': _("Existence of table"),
}),
('switch.table.active.entries', {
'type': _("SDN"),
'label': '',
'description': _("Active entries in table"),
}),
])
def _get_broadview_meters_info(self):
"""Returns additional info for each meter
That will be used for augmenting the Ceilometer meter
"""
# TODO(lsmola) Unless the Ceilometer will provide the information
# below, I need to define it as a static here. I will be joining this
# to info that I am able to obtain from Ceilometer meters, hopefully
# some day it will be supported all.
return datastructures.SortedDict([
('broadview.bst.device', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.ingress-port-priority-group', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.ingress-port-service-pool', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-cpu-queue', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-mc-queue', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-port-service-pool', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-rqe-queue', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-service-pool', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.bst.egress-uc-queue', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.pt.packet-trace-lag-resolution', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.pt.packet-trace-ecmp-resolution', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
('broadview.pt.packet-trace-profile', {
'type': _("BROADVIEW"),
'label': '',
'description': _("Existence of BST device"),
}),
])
def make_query(user_id=None, tenant_id=None, resource_id=None,
user_ids=None, tenant_ids=None, resource_ids=None):
"""Returns query built from given parameters.
This query can be then used for querying resources, meters and
statistics.
:Parameters:
- `user_id`: user_id, has a priority over list of ids
- `tenant_id`: tenant_id, has a priority over list of ids
- `resource_id`: resource_id, has a priority over list of ids
- `user_ids`: list of user_ids
- `tenant_ids`: list of tenant_ids
- `resource_ids`: list of resource_ids
"""
user_ids = user_ids or []
tenant_ids = tenant_ids or []
resource_ids = resource_ids or []
query = []
if user_id:
user_ids = [user_id]
for u_id in user_ids:
query.append({"field": "user_id", "op": "eq", "value": u_id})
if tenant_id:
tenant_ids = [tenant_id]
for t_id in tenant_ids:
query.append({"field": "project_id", "op": "eq", "value": t_id})
if resource_id:
resource_ids = [resource_id]
for r_id in resource_ids:
query.append({"field": "resource_id", "op": "eq", "value": r_id})
return query
def calc_date_args(date_from, date_to, date_options):
# TODO(lsmola) all timestamps should probably work with
# current timezone. And also show the current timezone in chart.
if date_options == "other":
try:
if date_from:
date_from = pytz.utc.localize(
datetime.datetime.strptime(str(date_from), "%Y-%m-%d"))
else:
# TODO(lsmola) there should be probably the date
# of the first sample as default, so it correctly
# counts the time window. Though I need ordering
# and limit of samples to obtain that.
pass
if date_to:
date_to = pytz.utc.localize(
datetime.datetime.strptime(str(date_to), "%Y-%m-%d"))
# It returns the beginning of the day, I want the end of
# the day, so I add one day without a second.
date_to = (date_to + datetime.timedelta(days=1) -
datetime.timedelta(seconds=1))
else:
date_to = timezone.now()
except Exception:
raise ValueError(_("The dates haven't been recognized"))
else:
try:
date_to = timezone.now()
date_from = date_to - datetime.timedelta(days=float(date_options))
except Exception as e:
raise e
#raise ValueError(_("The time delta must be a number representing "
# "the time span in days"))
return date_from, date_to
class MetersList(APIView):
method_kind = "list"
method_name = "meters"
def get(self, request, format=None):
if (not request.user.is_authenticated()):
raise PermissionDenied("You must be authenticated in order to use this API")
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
tenant_id = request.query_params.get('tenant', None)
resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
query.extend(make_query(tenant_id=tenant_id))
if resource_id:
query.extend(make_query(resource_id=resource_id))
tenant_map = getTenantControllerTenantMap(request.user)
resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
return Response(meters.get_all())
class MeterStatisticsList(APIView):
method_kind = "list"
method_name = "meterstatistics"
def get(self, request, format=None):
if (not request.user.is_authenticated()):
raise PermissionDenied("You must be authenticated in order to use this API")
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
tenant_map = getTenantControllerTenantMap(request.user)
date_options = request.query_params.get('period', 1)
date_from = request.query_params.get('date_from', '')
date_to = request.query_params.get('date_to', '')
try:
date_from, date_to = calc_date_args(date_from,
date_to,
date_options)
except Exception as e:
raise e
additional_query = []
if date_from:
additional_query.append({'field': 'timestamp',
'op': 'ge',
'value': date_from})
if date_to:
additional_query.append({'field': 'timestamp',
'op': 'le',
'value': date_to})
meter_name = request.query_params.get('meter', None)
tenant_id = request.query_params.get('tenant', None)
resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
query.extend(make_query(tenant_id=tenant_id))
if resource_id:
query.extend(make_query(resource_id=resource_id))
if meter_name:
#Statistics query for one meter
if additional_query:
query = query + additional_query
statistics = statistic_list(request, meter_name,
ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
statistic = statistics[-1]
row = {"name": 'none',
"meter": meter_name,
"time": statistic["period_end"],
"value": statistic["avg"]}
return Response(row)
#Statistics query for all meter
resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
report_rows = []
for meter in meters.get_all():
query = make_query(tenant_id=meter["project_id"],resource_id=meter["resource_id"])
if additional_query:
query = query + additional_query
try:
statistics = statistic_list(request, meter["name"],
ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
except Exception as e:
logger.error('Exception during statistics query for meter %(meter)s and reason:%(reason)s' % {'meter':meter["name"], 'reason':str(e)})
statistics = None
if not statistics:
continue
statistic = statistics[-1]
row = {"name": 'none',
"slice": meter["slice"],
"project_id": meter["project_id"],
"service": meter["service"],
"resource_id": meter["resource_id"],
"resource_name": meter["resource_name"],
"meter": meter["name"],
"description": meter["description"],
"category": meter["category"],
"time": statistic["period_end"],
"value": statistic["avg"],
"unit": meter["unit"]}
report_rows.append(row)
return Response(report_rows)
class MeterSamplesList(APIView):
method_kind = "list"
method_name = "metersamples"
def get(self, request, format=None):
if (not request.user.is_authenticated()):
raise PermissionDenied("You must be authenticated in order to use this API")
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
meter_name = request.query_params.get('meter', None)
if not meter_name:
raise XOSMissingField("Meter name in query params is missing")
limit = request.query_params.get('limit', 10)
tenant_id = request.query_params.get('tenant', None)
resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
query.extend(make_query(tenant_id=tenant_id))
if resource_id:
query.extend(make_query(resource_id=resource_id))
query.append({"field": "meter", "op": "eq", "value": meter_name})
samples = sample_list(request, meter_name,
ceilometer_url=tenant_ceilometer_url, query=query, limit=limit)
if samples:
tenant_map = getTenantControllerTenantMap(request.user)
resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url)
for sample in samples:
if sample["project_id"] in tenant_map.keys():
sample["slice"] = tenant_map[sample["project_id"]]["slice"]
else:
sample["slice"] = sample["project_id"]
if sample["resource_id"] in resource_map.keys():
sample["resource_name"] = resource_map[sample["resource_id"]]
else:
sample["resource_name"] = sample["resource_id"]
return Response(samples)
class XOSSliceServiceList(APIView):
method_kind = "list"
method_name = "xos-slice-service-mapping"
def get(self, request, format=None):
if (not request.user.is_authenticated()):
raise PermissionDenied("You must be authenticated in order to use this API")
tenant_map = getTenantControllerTenantMap(request.user)
service_map={}
for k,v in tenant_map.iteritems():
if not (v['service'] in service_map.keys()):
service_map[v['service']] = {}
service_map[v['service']]['service'] = v['service']
service_map[v['service']]['slices'] = []
slice_details = {'slice':v['slice'], 'project_id':k}
service_map[v['service']]['slices'].append(slice_details)
return Response(service_map.values())
class XOSInstanceStatisticsList(APIView):
method_kind = "list"
method_name = "xos-instance-statistics"
def get(self, request, format=None):
if (not request.user.is_authenticated()):
raise PermissionDenied("You must be authenticated in order to use this API")
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
instance_uuid = request.query_params.get('instance-uuid', None)
if not instance_uuid:
raise XOSMissingField("Instance UUID in query params is missing")
if not Instance.objects.filter(instance_uuid=instance_uuid):
raise XOSMissingField("XOS Instance object is missing for this uuid")
xos_instance = Instance.objects.filter(instance_uuid=instance_uuid)[0]
tenant_map = getTenantControllerTenantMap(request.user, xos_instance.slice)
tenant_id = tenant_map.keys()[0]
resource_ids = []
resource_ids.append(instance_uuid)
for p in xos_instance.ports.all():
#neutron port resource id is represented in ceilometer as "nova instance-name"+"-"+"nova instance-id"+"-"+"tap"+first 11 characters of port-id
resource_ids.append(xos_instance.instance_id+"-"+instance_uuid+"-tap"+p.port_id[:11])
date_options = request.query_params.get('period', 1)
date_from = request.query_params.get('date_from', '')
date_to = request.query_params.get('date_to', '')
try:
date_from, date_to = calc_date_args(date_from,
date_to,
date_options)
except Exception as e:
raise e
additional_query = []
if date_from:
additional_query.append({'field': 'timestamp',
'op': 'ge',
'value': date_from})
if date_to:
additional_query.append({'field': 'timestamp',
'op': 'le',
'value': date_to})
report_rows = []
for resource_id in resource_ids:
query = []
if tenant_id:
query.extend(make_query(tenant_id=tenant_id))
if resource_id:
query.extend(make_query(resource_id=resource_id))
#Statistics query for all meter
resource_map = get_resource_map(request, ceilometer_url=tenant_ceilometer_url, query=query)
meters = Meters(request, ceilometer_url=tenant_ceilometer_url, query=query, tenant_map=tenant_map, resource_map=resource_map)
exclude_nova_meters_info = [ "instance", "instance:<type>", "disk.read.requests", "disk.write.requests",
"disk.read.bytes", "disk.write.bytes", "disk.read.requests.rate", "disk.write.requests.rate", "disk.read.bytes.rate",
"disk.write.bytes.rate", "disk.root.size", "disk.ephemeral.size"]
exclude_neutron_meters_info = [ 'network.create', 'network.update', 'subnet.create',
'subnet.update', 'port.create', 'port.update', 'router.create', 'router.update',
'ip.floating.create', 'ip.floating.update']
services = {
_('Nova'): meters.list_nova(except_meters=exclude_nova_meters_info),
_('Neutron'): meters.list_neutron(except_meters=exclude_neutron_meters_info),
_('VSG'): meters.list_vcpe(),
_('VOLT'): meters.list_volt(),
_('SDN'): meters.list_sdn(),
_('BROADVIEW'): meters.list_broadview(),
}
for service,meters in services.items():
for meter in meters:
query = make_query(tenant_id=meter["project_id"],resource_id=meter["resource_id"])
if additional_query:
query = query + additional_query
try:
statistics = statistic_list(request, meter["name"],
ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
except Exception as e:
logger.error('Exception during statistics query for meter %(meter)s and reason:%(reason)s' % {'meter':meter["name"], 'reason':str(e)})
statistics = None
if not statistics:
continue
statistic = statistics[-1]
row = {"name": 'none',
"slice": meter["slice"],
"project_id": meter["project_id"],
"service": meter["service"],
"resource_id": meter["resource_id"],
"resource_name": meter["resource_name"],
"meter": meter["name"],
"description": meter["description"],
"category": service,
"time": statistic["period_end"],
"value": statistic["avg"],
"unit": meter["unit"]}
report_rows.append(row)
return Response(report_rows)
class ServiceAdjustScale(APIView):
method_kind = "list"
method_name = "serviceadjustscale"
def get(self, request, format=None):
if (not request.user.is_authenticated()) or (not request.user.is_admin):
raise PermissionDenied("You must be authenticated admin user in order to use this API")
service = request.query_params.get('service', None)
slice_hint = request.query_params.get('slice_hint', None)
scale = request.query_params.get('scale', None)
if not service or not slice_hint or not scale:
raise XOSMissingField("Mandatory fields missing")
services = Service.select_by_user(request.user)
logger.info('SRIKANTH: Services for this user %(services)s' % {'services':services})
if not services or (not services.get(name=service)):
raise XOSMissingField("Service not found")
service = services.get(name=service)
service.adjust_scale(slice_hint, int(scale))
return Response("Success")