Enable quota data from multiple sources.

Now that there are multiple projects with quota data (cinder, quantum)
we need to accommodate that data being aggregated in a centralized
fashion. This commit takes care of that for nova + cinder, and paves
the way for quantum later.

Fixes bug 1070022.

Change-Id: Ifc68c2dc681b2a7b4e7787e0b1a7dca1a970fc36
This commit is contained in:
Gabriel Hurley 2012-10-23 22:25:18 -07:00
parent a0ad0d49a9
commit cdcd8e3df6
28 changed files with 626 additions and 431 deletions

View File

@ -140,7 +140,7 @@ horizon.Quota = {
element.removeClass('progress_bar_over');
}
element.animate({width: update_width + "%"}, 300);
element.animate({width: parseInt(update_width, 10) + "%"}, 300);
},
/*

View File

@ -45,3 +45,6 @@ class memoized(object):
def __get__(self, obj, objtype):
'''Support instance methods.'''
return functools.partial(self.__call__, obj)
def __str__(self):
return str(self.func)

View File

@ -37,3 +37,4 @@ from openstack_dashboard.api.keystone import *
from openstack_dashboard.api.nova import *
from openstack_dashboard.api.swift import *
from openstack_dashboard.api.quantum import *
from openstack_dashboard.api.cinder import *

View File

@ -18,6 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from collections import Sequence
import logging
from django.conf import settings
@ -92,6 +93,53 @@ class APIDictWrapper(object):
return default
class Quota(object):
"""Wrapper for individual limits in a quota."""
def __init__(self, name, limit):
self.name = name
self.limit = limit
def __repr__(self):
return "<Quota: (%s, %s)>" % (self.name, self.limit)
class QuotaSet(Sequence):
"""
Wrapper for client QuotaSet objects which turns the individual quotas
into Quota objects for easier handling/iteration.
`QuotaSet` objects support a mix of `list` and `dict` methods; you can use
the bracket notiation (`qs["my_quota"] = 0`) to add new quota values, and
use the `get` method to retrieve a specific quota, but otherwise it
behaves much like a list or tuple, particularly in supporting iteration.
"""
def __init__(self, apiresource=None):
self.items = []
if apiresource:
for k, v in apiresource._info.items():
if k == 'id':
continue
self[k] = v
def __setitem__(self, k, v):
v = int(v) if v is not None else v
q = Quota(k, v)
self.items.append(q)
def __getitem__(self, index):
return self.items[index]
def __len__(self):
return len(self.items)
def __repr__(self):
return repr(self.items)
def get(self, key, default=None):
match = [quota for quota in self.items if quota.name == key]
return match.pop() if len(match) else Quota(key, default)
def get_service_from_catalog(catalog, service_type):
if catalog:
for service in catalog:
@ -116,3 +164,12 @@ def url_for(request, service_type, admin=False, endpoint_type=None):
raise exceptions.ServiceCatalogException(service_type)
else:
raise exceptions.ServiceCatalogException(service_type)
def is_service_enabled(request, service_type, service_name=None):
service = get_service_from_catalog(request.user.service_catalog,
service_type)
if service and service_name:
return service['name'] == service_name
else:
return service is not None

View File

@ -0,0 +1,115 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Openstack, LLC
# Copyright 2012 Nebula, Inc.
# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
#
# 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.
from __future__ import absolute_import
import logging
from django.conf import settings
from django.utils.translation import ugettext as _
from cinderclient.v1 import client as cinder_client
from openstack_dashboard.api.base import url_for
from openstack_dashboard.api import nova, QuotaSet
LOG = logging.getLogger(__name__)
# API static values
VOLUME_STATE_AVAILABLE = "available"
def cinderclient(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
LOG.debug('cinderclient connection created using token "%s" and url "%s"' %
(request.user.token.id, url_for(request, 'volume')))
c = cinder_client.Client(request.user.username,
request.user.token.id,
project_id=request.user.tenant_id,
auth_url=url_for(request, 'volume'),
insecure=insecure)
c.client.auth_token = request.user.token.id
c.client.management_url = url_for(request, 'volume')
return c
def volume_list(request, search_opts=None):
"""
To see all volumes in the cloud as an admin you can pass in a special
search option: {'all_tenants': 1}
"""
return cinderclient(request).volumes.list(search_opts=search_opts)
def volume_get(request, volume_id):
volume_data = cinderclient(request).volumes.get(volume_id)
for attachment in volume_data.attachments:
if "server_id" in attachment:
instance = nova.server_get(request, attachment['server_id'])
attachment['instance_name'] = instance.name
else:
# Nova volume can occasionally send back error'd attachments
# the lack a server_id property; to work around that we'll
# give the attached instance a generic name.
attachment['instance_name'] = _("Unknown instance")
return volume_data
def volume_create(request, size, name, description, snapshot_id=None):
return cinderclient(request).volumes.create(size, display_name=name,
display_description=description, snapshot_id=snapshot_id)
def volume_delete(request, volume_id):
return cinderclient(request).volumes.delete(volume_id)
def volume_snapshot_get(request, snapshot_id):
return cinderclient(request).volume_snapshots.get(snapshot_id)
def volume_snapshot_list(request):
return cinderclient(request).volume_snapshots.list()
def volume_snapshot_create(request, volume_id, name, description):
return cinderclient(request).volume_snapshots.create(
volume_id, display_name=name, display_description=description)
def volume_snapshot_delete(request, snapshot_id):
return cinderclient(request).volume_snapshots.delete(snapshot_id)
def tenant_quota_get(request, tenant_id):
return QuotaSet(cinderclient(request).quotas.get(tenant_id))
def tenant_quota_update(request, tenant_id, **kwargs):
return cinderclient(request).quotas.update(tenant_id, **kwargs)
def default_quota_get(request, tenant_id):
return QuotaSet(cinderclient(request).quotas.defaults(tenant_id))

View File

@ -27,17 +27,14 @@ import logging
from django.conf import settings
from django.utils.translation import ugettext as _
from cinderclient.v1 import client as cinder_client
from novaclient.v1_1 import client as nova_client
from novaclient.v1_1 import security_group_rules as nova_rules
from novaclient.v1_1.security_groups import SecurityGroup as NovaSecurityGroup
from novaclient.v1_1.servers import REBOOT_HARD
from horizon import exceptions
from horizon.utils.memoized import memoized
from openstack_dashboard.api.base import (APIResourceWrapper,
from openstack_dashboard.api.base import (APIResourceWrapper, QuotaSet,
APIDictWrapper, url_for)
@ -56,32 +53,6 @@ class VNCConsole(APIDictWrapper):
_attrs = ['url', 'type']
class Quota(object):
"""Wrapper for individual limits in a quota."""
def __init__(self, name, limit):
self.name = name
self.limit = limit
def __repr__(self):
return "<Quota: (%s, %s)>" % (self.name, self.limit)
class QuotaSet(object):
"""Wrapper for novaclient.quotas.QuotaSet objects which wraps the
individual quotas inside Quota objects.
"""
def __init__(self, apiresource):
self.items = []
for k in apiresource._info.keys():
if k in ['id']:
continue
limit = apiresource._info[k]
v = int(limit) if limit is not None else limit
q = Quota(k, v)
self.items.append(q)
setattr(self, k, v)
class Server(APIResourceWrapper):
"""Simple wrapper around novaclient.server.Server
@ -117,7 +88,7 @@ class Server(APIResourceWrapper):
novaclient(self.request).servers.reboot(self.id, hardness)
class Usage(APIResourceWrapper):
class NovaUsage(APIResourceWrapper):
"""Simple wrapper around contrib/simple_usage.py."""
_attrs = ['start', 'server_usages', 'stop', 'tenant_id',
'total_local_gb_usage', 'total_memory_mb_usage',
@ -210,20 +181,6 @@ def novaclient(request):
return c
def cinderclient(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
LOG.debug('cinderclient connection created using token "%s" and url "%s"' %
(request.user.token.id, url_for(request, 'volume')))
c = cinder_client.Client(request.user.username,
request.user.token.id,
project_id=request.user.tenant_id,
auth_url=url_for(request, 'volume'),
insecure=insecure)
c.client.auth_token = request.user.token.id
c.client.management_url = url_for(request, 'volume')
return c
def server_vnc_console(request, instance_id, console_type='novnc'):
return VNCConsole(novaclient(request).servers.get_vnc_console(instance_id,
console_type)['console'])
@ -408,68 +365,17 @@ def tenant_quota_update(request, tenant_id, **kwargs):
novaclient(request).quotas.update(tenant_id, **kwargs)
def tenant_quota_defaults(request, tenant_id):
def default_quota_get(request, tenant_id):
return QuotaSet(novaclient(request).quotas.defaults(tenant_id))
def usage_get(request, tenant_id, start, end):
return Usage(novaclient(request).usage.get(tenant_id, start, end))
return NovaUsage(novaclient(request).usage.get(tenant_id, start, end))
def usage_list(request, start, end):
return [Usage(u) for u in novaclient(request).usage.list(start, end, True)]
@memoized
def tenant_quota_usages(request):
"""
Builds a dictionary of current usage against quota for the current
project.
"""
instances = server_list(request)
floating_ips = tenant_floating_ip_list(request)
quotas = tenant_quota_get(request, request.user.tenant_id)
flavors = dict([(f.id, f) for f in flavor_list(request)])
volumes = volume_list(request)
usages = {'instances': {'flavor_fields': [], 'used': len(instances)},
'cores': {'flavor_fields': ['vcpus'], 'used': 0},
'gigabytes': {'used': sum([int(v.size) for v in volumes]),
'flavor_fields': []},
'volumes': {'used': len(volumes), 'flavor_fields': []},
'ram': {'flavor_fields': ['ram'], 'used': 0},
'floating_ips': {'flavor_fields': [], 'used': len(floating_ips)}}
for usage in usages:
for instance in instances:
used_flavor = instance.flavor['id']
if used_flavor not in flavors:
try:
flavors[used_flavor] = flavor_get(request, used_flavor)
except:
flavors[used_flavor] = {}
exceptions.handle(request, ignore=True)
for flavor_field in usages[usage]['flavor_fields']:
instance_flavor = flavors[used_flavor]
usages[usage]['used'] += getattr(instance_flavor,
flavor_field,
0)
usages[usage]['quota'] = getattr(quotas, usage)
if usages[usage]['quota'] is None:
usages[usage]['quota'] = float("inf")
usages[usage]['available'] = float("inf")
elif type(usages[usage]['quota']) is str:
usages[usage]['quota'] = int(usages[usage]['quota'])
else:
if type(usages[usage]['used']) is str:
usages[usage]['used'] = int(usages[usage]['used'])
usages[usage]['available'] = usages[usage]['quota'] - \
usages[usage]['used']
return usages
return [NovaUsage(u) for u in
novaclient(request).usage.list(start, end, True)]
def security_group_list(request):
@ -510,30 +416,28 @@ def virtual_interfaces_list(request, instance_id):
return novaclient(request).virtual_interfaces.list(instance_id)
def volume_list(request, search_opts=None):
"""
To see all volumes in the cloud as an admin you can pass in a special
search option: {'all_tenants': 1}
"""
return cinderclient(request).volumes.list(search_opts=search_opts)
def get_x509_credentials(request):
return novaclient(request).certs.create()
def volume_get(request, volume_id):
volume_data = cinderclient(request).volumes.get(volume_id)
for attachment in volume_data.attachments:
if "server_id" in attachment:
instance = server_get(request, attachment['server_id'])
attachment['instance_name'] = instance.name
else:
# Nova volume can occasionally send back error'd attachments
# the lack a server_id property; to work around that we'll
# give the attached instance a generic name.
attachment['instance_name'] = _("Unknown instance")
return volume_data
def get_x509_root_certificate(request):
return novaclient(request).certs.get()
def volume_instance_list(request, instance_id):
def instance_volume_attach(request, volume_id, instance_id, device):
return novaclient(request).volumes.create_server_volume(instance_id,
volume_id,
device)
def instance_volume_detach(request, instance_id, att_id):
return novaclient(request).volumes.delete_server_volume(instance_id,
att_id)
def instance_volumes_list(request, instance_id):
from openstack_dashboard.api.cinder import cinderclient
volumes = novaclient(request).volumes.get_server_volumes(instance_id)
for volume in volumes:
@ -541,47 +445,3 @@ def volume_instance_list(request, instance_id):
volume.name = volume_data.display_name
return volumes
def volume_create(request, size, name, description, snapshot_id=None):
return cinderclient(request).volumes.create(size, display_name=name,
display_description=description, snapshot_id=snapshot_id)
def volume_delete(request, volume_id):
cinderclient(request).volumes.delete(volume_id)
def volume_attach(request, volume_id, instance_id, device):
return novaclient(request).volumes.create_server_volume(instance_id,
volume_id,
device)
def volume_detach(request, instance_id, att_id):
novaclient(request).volumes.delete_server_volume(instance_id, att_id)
def volume_snapshot_get(request, snapshot_id):
return cinderclient(request).volume_snapshots.get(snapshot_id)
def volume_snapshot_list(request):
return cinderclient(request).volume_snapshots.list()
def volume_snapshot_create(request, volume_id, name, description):
return cinderclient(request).volume_snapshots.create(
volume_id, display_name=name, display_description=description)
def volume_snapshot_delete(request, snapshot_id):
cinderclient(request).volume_snapshots.delete(snapshot_id)
def get_x509_credentials(request):
return novaclient(request).certs.create()
def get_x509_root_certificate(request):
return novaclient(request).certs.get()

View File

@ -29,8 +29,9 @@ from mox import IsA, Func
from horizon.templatetags.sizeformat import mbformat
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard import usage
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:overview:index')
@ -38,19 +39,19 @@ INDEX_URL = reverse('horizon:project:overview:index')
class UsageViewTests(test.BaseAdminViewTests):
@test.create_stubs({api: ('usage_list',),
api.nova: ('tenant_quota_usages',),
quotas: ('tenant_quota_usages',),
api.keystone: ('tenant_list',)})
def test_usage(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
quotas = self.quota_usages.first()
usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True) \
.AndReturn(self.tenants.list())
api.usage_list(IsA(http.HttpRequest),
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \
.AndReturn([usage_obj])
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:admin:overview:index'))
self.assertTemplateUsed(res, 'admin/overview/usage.html')
@ -70,19 +71,19 @@ class UsageViewTests(test.BaseAdminViewTests):
usage_obj.total_local_gb_usage))
@test.create_stubs({api: ('usage_list',),
api.nova: ('tenant_quota_usages',),
quotas: ('tenant_quota_usages',),
api.keystone: ('tenant_list',)})
def test_usage_csv(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
quotas = self.quota_usages.first()
usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True) \
.AndReturn(self.tenants.list())
api.usage_list(IsA(http.HttpRequest),
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \
.AndReturn([usage_obj])
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
csv_url = reverse('horizon:admin:overview:index') + "?format=csv"
res = self.client.get(csv_url)

View File

@ -21,6 +21,7 @@ from mox import IsA
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
from .workflows import CreateProject, UpdateProject
from .views import QUOTA_FIELDS
@ -55,7 +56,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
def _get_quota_info(self, quota):
quota_data = {}
for field in QUOTA_FIELDS:
quota_data[field] = int(getattr(quota, field, None))
quota_data[field] = int(quota.get(field).limit)
return quota_data
def _get_workflow_data(self, project, quota):
@ -64,18 +65,17 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
project_info.update(quota_data)
return project_info
@test.create_stubs({api: ('tenant_quota_defaults',
'get_default_role',),
api.keystone: ('user_list',
'role_list',)})
@test.create_stubs({api: ('get_default_role',),
quotas: ('get_default_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_add_project_get(self):
quota = self.quotas.first()
default_role = self.roles.first()
users = self.users.list()
roles = self.roles.list()
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
# init
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
@ -93,20 +93,21 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertEqual(res.context['workflow'].name, CreateProject.name)
step = workflow.get_step("createprojectinfoaction")
self.assertEqual(step.action.initial['ram'], quota.ram)
self.assertEqual(step.action.initial['ram'], quota.get('ram').limit)
self.assertEqual(step.action.initial['injected_files'],
quota.injected_files)
quota.get('injected_files').limit)
self.assertQuerysetEqual(workflow.steps,
['<CreateProjectInfo: createprojectinfoaction>',
'<UpdateProjectMembers: update_members>',
'<UpdateProjectQuota: update_quotas>'])
@test.create_stubs({api: ('get_default_role',
'tenant_quota_defaults',
'add_tenant_user_role',),
api.keystone: ('tenant_create',
'user_list',
'role_list'),
quotas: ('get_default_quota_data',),
api.cinder: ('tenant_quota_update',),
api.nova: ('tenant_quota_update',)})
def test_add_project_post(self):
project = self.tenants.first()
@ -116,8 +117,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -159,8 +159,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('tenant_quota_defaults',
'get_default_role',),
@test.create_stubs({api: ('get_default_role',),
quotas: ('get_default_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_add_project_quota_defaults_error(self):
@ -169,8 +169,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndRaise(self.exceptions.nova)
quotas.get_default_quota_data(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -184,8 +184,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertTemplateUsed(res, 'admin/projects/create.html')
self.assertContains(res, "Unable to retrieve default quota values")
@test.create_stubs({api: ('get_default_role',
'tenant_quota_defaults',),
@test.create_stubs({api: ('get_default_role',),
quotas: ('get_default_quota_data',),
api.keystone: ('tenant_create',
'user_list',
'role_list',)})
@ -197,8 +197,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -224,11 +223,11 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('get_default_role',
'tenant_quota_defaults',
'add_tenant_user_role',),
api.keystone: ('tenant_create',
'user_list',
'role_list'),
quotas: ('get_default_quota_data',),
api.nova: ('tenant_quota_update',)})
def test_add_project_quota_update_error(self):
project = self.tenants.first()
@ -238,8 +237,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -283,11 +281,11 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('get_default_role',
'tenant_quota_defaults',
'add_tenant_user_role',),
api.keystone: ('tenant_create',
'user_list',
'role_list',),
quotas: ('get_default_quota_data',),
api.nova: ('tenant_quota_update',)})
def test_add_project_user_update_error(self):
project = self.tenants.first()
@ -297,8 +295,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -343,8 +340,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('get_default_role',
'tenant_quota_defaults',),
@test.create_stubs({api: ('get_default_role',),
quotas: ('get_default_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_add_project_missing_field_error(self):
@ -355,8 +352,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
roles = self.roles.list()
# init
api.tenant_quota_defaults(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -380,13 +376,13 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
def _get_quota_info(self, quota):
quota_data = {}
for field in QUOTA_FIELDS:
quota_data[field] = int(getattr(quota, field, None))
quota_data[field] = int(quota.get(field).limit)
return quota_data
@test.create_stubs({api: ('get_default_role',
'roles_for_user',
'tenant_get',
'tenant_quota_get',),
'tenant_get',),
quotas: ('get_tenant_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_update_project_get(self):
@ -398,8 +394,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.tenant_get(IsA(http.HttpRequest), self.tenant.id, admin=True) \
.AndReturn(project)
api.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_tenant_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -422,9 +417,9 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertEqual(res.context['workflow'].name, UpdateProject.name)
step = workflow.get_step("update_info")
self.assertEqual(step.action.initial['ram'], quota.ram)
self.assertEqual(step.action.initial['ram'], quota.get('ram').limit)
self.assertEqual(step.action.initial['injected_files'],
quota.injected_files)
quota.get('injected_files').limit)
self.assertEqual(step.action.initial['name'], project.name)
self.assertEqual(step.action.initial['description'],
project.description)
@ -434,13 +429,14 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
'<UpdateProjectQuota: update_quotas>'])
@test.create_stubs({api: ('tenant_get',
'tenant_quota_get',
'tenant_update',
'tenant_quota_update',
'get_default_role',
'roles_for_user',
'remove_tenant_user_role',
'add_tenant_user_role'),
api.nova: ('tenant_quota_update',),
api.cinder: ('tenant_quota_update',),
quotas: ('get_tenant_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_update_project_save(self):
@ -453,8 +449,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
# get/init
api.tenant_get(IsA(http.HttpRequest), self.tenant.id, admin=True) \
.AndReturn(project)
api.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_tenant_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -524,9 +519,13 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user_id='3',
role_id='1')
api.tenant_quota_update(IsA(http.HttpRequest),
api.nova.tenant_quota_update(IsA(http.HttpRequest),
project.id,
**updated_quota)
api.cinder.tenant_quota_update(IsA(http.HttpRequest),
project.id,
volumes=updated_quota['volumes'],
gigabytes=updated_quota['gigabytes'])
self.mox.ReplayAll()
# submit form data
@ -559,13 +558,13 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('tenant_get',
'tenant_quota_get',
'tenant_update',
'tenant_quota_update',
'get_default_role',
'roles_for_user',
'remove_tenant_user',
'add_tenant_user_role'),
quotas: ('get_tenant_quota_data',),
api.nova: ('tenant_quota_update',),
api.keystone: ('user_list',
'role_list',)})
def test_update_project_tenant_update_error(self):
@ -578,8 +577,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
# get/init
api.tenant_get(IsA(http.HttpRequest), self.tenant.id, admin=True) \
.AndReturn(project)
api.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_tenant_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -631,13 +629,13 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('tenant_get',
'tenant_quota_get',
'tenant_update',
'tenant_quota_update',
'get_default_role',
'roles_for_user',
'remove_tenant_user_role',
'add_tenant_user_role'),
quotas: ('get_tenant_quota_data',),
api.nova: ('tenant_quota_update',),
api.keystone: ('user_list',
'role_list',)})
def test_update_project_quota_update_error(self):
@ -650,8 +648,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
# get/init
api.tenant_get(IsA(http.HttpRequest), self.tenant.id, admin=True) \
.AndReturn(project)
api.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_tenant_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)
@ -708,7 +705,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
user_id='3',
role_id='2')
api.tenant_quota_update(IsA(http.HttpRequest),
api.nova.tenant_quota_update(IsA(http.HttpRequest),
project.id,
**updated_quota).AndRaise(self.exceptions.nova)
@ -730,12 +727,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('tenant_get',
'tenant_quota_get',
'tenant_update',
'get_default_role',
'roles_for_user',
'remove_tenant_user_role',
'add_tenant_user_role'),
quotas: ('get_tenant_quota_data',),
api.keystone: ('user_list',
'role_list',)})
def test_update_project_member_update_error(self):
@ -748,8 +745,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
# get/init
api.tenant_get(IsA(http.HttpRequest), self.tenant.id, admin=True) \
.AndReturn(project)
api.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(quota)
quotas.get_tenant_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.get_default_role(IsA(http.HttpRequest)).AndReturn(default_role)
api.keystone.user_list(IsA(http.HttpRequest)).AndReturn(users)

View File

@ -29,6 +29,7 @@ from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard import usage
from openstack_dashboard.usage import quotas
from openstack_dashboard.dashboards.admin.users.views import CreateView
from .forms import CreateUser
from .tables import TenantsTable, TenantUsersTable, AddUsersTable
@ -145,10 +146,9 @@ class CreateProjectView(workflows.WorkflowView):
# get initial quota defaults
try:
quota_defaults = api.tenant_quota_defaults(self.request,
self.request.user.tenant_id)
quota_defaults = quotas.get_default_quota_data(self.request)
for field in QUOTA_FIELDS:
initial[field] = getattr(quota_defaults, field, None)
initial[field] = quota_defaults.get(field).limit
except:
error_msg = _('Unable to retrieve default quota values.')
@ -174,9 +174,9 @@ class UpdateProjectView(workflows.WorkflowView):
initial[field] = getattr(project_info, field, None)
# get initial project quota
quota_data = api.tenant_quota_get(self.request, project_id)
quota_data = quotas.get_tenant_quota_data(self.request)
for field in QUOTA_FIELDS:
initial[field] = getattr(quota_data, field, None)
initial[field] = quota_data.get(field).limit
except:
exceptions.handle(self.request,
_('Unable to retrieve project details.'),

View File

@ -28,6 +28,8 @@ from horizon import forms
from horizon import messages
from openstack_dashboard import api
from openstack_dashboard.api import cinder, nova
from openstack_dashboard.api.base import is_service_enabled
INDEX_URL = "horizon:admin:projects:index"
@ -361,17 +363,25 @@ class UpdateProject(workflows.Workflow):
# update the project quota
ifcb = data['injected_file_content_bytes']
try:
api.tenant_quota_update(request,
project_id,
metadata_items=data['metadata_items'],
injected_file_content_bytes=ifcb,
volumes=data['volumes'],
gigabytes=data['gigabytes'],
ram=data['ram'],
floating_ips=data['floating_ips'],
instances=data['instances'],
injected_files=data['injected_files'],
cores=data['cores'])
# TODO(gabriel): Once nova-volume is fully deprecated the
# "volumes" and "gigabytes" quotas should no longer be sent to
# the nova API to be updated anymore.
nova.tenant_quota_update(request,
project_id,
metadata_items=data['metadata_items'],
injected_file_content_bytes=ifcb,
volumes=data['volumes'],
gigabytes=data['gigabytes'],
ram=data['ram'],
floating_ips=data['floating_ips'],
instances=data['instances'],
injected_files=data['injected_files'],
cores=data['cores'])
if is_service_enabled(request, 'volume'):
cinder.tenant_quota_update(request,
project_id,
volumes=data['volumes'],
gigabytes=data['gigabytes'])
return True
except:
exceptions.handle(request, _('Modified project information and '

View File

@ -38,7 +38,7 @@ class IndexView(tables.DataTableView):
def get_data(self):
try:
quota_set = api.tenant_quota_defaults(self.request,
quota_set = api.default_quota_get(self.request,
self.request.user.tenant_id)
data = quota_set.items
except:

View File

@ -31,6 +31,7 @@ from horizon import forms
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.usage import quotas
from .forms import FloatingIpAllocate
from .workflows import IPAssociationWorkflow
@ -51,7 +52,7 @@ class AllocateView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(AllocateView, self).get_context_data(**kwargs)
try:
context['usages'] = api.tenant_quota_usages(self.request)
context['usages'] = quotas.tenant_quota_usages(self.request)
except:
exceptions.handle(self.request)
return context

View File

@ -24,6 +24,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from ...volumes import tables as volume_tables
@ -57,7 +58,7 @@ class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, snapshot_id):
snapshot = api.nova.volume_snapshot_get(request, snapshot_id)
snapshot = cinder.volume_snapshot_get(request, snapshot_id)
return snapshot

View File

@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.api import cinder
class OverviewTab(tabs.Tab):
@ -32,8 +32,8 @@ class OverviewTab(tabs.Tab):
def get_context_data(self, request):
snapshot_id = self.tab_group.kwargs['snapshot_id']
try:
snapshot = api.nova.volume_snapshot_get(request, snapshot_id)
volume = api.nova.volume_get(request, snapshot.volume_id)
snapshot = cinder.volume_snapshot_get(request, snapshot_id)
volume = cinder.volume_get(request, snapshot.volume_id)
volume.display_name = None
except:
redirect = reverse('horizon:project:images_and_snapshots:index')

View File

@ -26,7 +26,9 @@ from django.utils.datastructures import SortedDict
from mox import IsA, IgnoreArg
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
from .tabs import InstanceDetailTabs
from .workflows import LaunchInstance
@ -324,7 +326,7 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"instance_volumes_list",
"flavor_get",
"server_security_groups")})
def test_instance_details_volumes(self):
@ -332,8 +334,8 @@ class InstanceTests(test.TestCase):
volumes = [self.volumes.list()[1]]
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_instance_list(IsA(http.HttpRequest),
server.id).AndReturn(volumes)
api.instance_volumes_list(IsA(http.HttpRequest),
server.id).AndReturn(volumes)
api.flavor_get(IsA(http.HttpRequest),
server.flavor['id']).AndReturn(self.flavors.first())
api.server_security_groups(IsA(http.HttpRequest),
@ -348,7 +350,7 @@ class InstanceTests(test.TestCase):
self.assertItemsEqual(res.context['instance'].volumes, volumes)
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"instance_volumes_list",
"flavor_get",
"server_security_groups")})
def test_instance_details_volume_sorting(self):
@ -356,8 +358,8 @@ class InstanceTests(test.TestCase):
volumes = self.volumes.list()[1:3]
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_instance_list(IsA(http.HttpRequest),
server.id).AndReturn(volumes)
api.instance_volumes_list(IsA(http.HttpRequest),
server.id).AndReturn(volumes)
api.flavor_get(IsA(http.HttpRequest),
server.flavor['id']).AndReturn(self.flavors.first())
api.server_security_groups(IsA(http.HttpRequest),
@ -376,15 +378,15 @@ class InstanceTests(test.TestCase):
"/dev/hdk")
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"instance_volumes_list",
"flavor_get",
"server_security_groups",)})
def test_instance_details_metadata(self):
server = self.servers.first()
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_instance_list(IsA(http.HttpRequest),
server.id).AndReturn([])
api.instance_volumes_list(IsA(http.HttpRequest),
server.id).AndReturn([])
api.flavor_get(IsA(http.HttpRequest),
server.flavor['id']).AndReturn(self.flavors.first())
api.server_security_groups(IsA(http.HttpRequest),
@ -582,30 +584,30 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.nova: ('tenant_quota_usages',
'flavor_list',
@test.create_stubs({api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_snapshot_list',
'volume_list',),
'security_group_list',),
cinder: ('volume_snapshot_list',
'volume_list',),
quotas: ('tenant_quota_usages',),
api.quantum: ('network_list',),
api.glance: ('image_list_detailed',)})
def test_launch_instance_get(self):
quota_usages = self.quota_usages.first()
image = self.images.first()
api.nova.volume_list(IsA(http.HttpRequest)) \
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([self.images.list(), False])
.AndReturn([self.images.list(), False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
.AndReturn([[], False])
api.quantum.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False) \
@ -613,7 +615,7 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -646,13 +648,13 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'volume_snapshot_list',
'tenant_quota_usages',
'server_create',)})
'server_create',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post(self):
flavor = self.flavors.first()
image = self.images.first()
@ -687,9 +689,9 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.volume_list(IsA(http.HttpRequest)) \
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.nova.server_create(IsA(http.HttpRequest),
server.name,
image.id,
@ -725,12 +727,12 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'tenant_quota_usages',
'volume_snapshot_list',)})
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post_no_images_available(self):
flavor = self.flavors.first()
keypair = self.keypairs.first()
@ -743,7 +745,7 @@ class InstanceTests(test.TestCase):
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn({})
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn({})
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
@ -765,9 +767,9 @@ class InstanceTests(test.TestCase):
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.nova.volume_list(IsA(http.HttpRequest)) \
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
self.mox.ReplayAll()
@ -795,16 +797,16 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
api.nova: ('tenant_quota_usages',
'flavor_list',
quotas: ('tenant_quota_usages',),
cinder: ('volume_list',
'volume_snapshot_list',),
api.nova: ('flavor_list',
'keypair_list',
'volume_list',
'security_group_list',
'volume_snapshot_list',)})
'security_group_list',)})
def test_launch_flavorlist_error(self):
api.nova.volume_list(IsA(http.HttpRequest)) \
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
@ -821,7 +823,7 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first())
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova)
@ -845,9 +847,9 @@ class InstanceTests(test.TestCase):
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'server_create',
'volume_snapshot_list',)})
'server_create',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_form_keystone_exception(self):
flavor = self.flavors.first()
image = self.images.first()
@ -857,8 +859,8 @@ class InstanceTests(test.TestCase):
customization_script = 'userData'
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
api.nova.keypair_list(IgnoreArg()).AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
@ -878,7 +880,7 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
cinder.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
api.nova.server_create(IsA(http.HttpRequest),
server.name,
image.id,
@ -912,12 +914,12 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'tenant_quota_usages',
'volume_snapshot_list',)})
'security_group_list',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_form_instance_count_error(self):
flavor = self.flavors.first()
image = self.images.first()
@ -950,13 +952,13 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.volume_list(IsA(http.HttpRequest)) \
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first())
self.mox.ReplayAll()

View File

@ -165,8 +165,8 @@ class DetailView(tabs.TabView):
try:
instance_id = self.kwargs['instance_id']
instance = api.server_get(self.request, instance_id)
instance.volumes = api.volume_instance_list(self.request,
instance_id)
instance.volumes = api.instance_volumes_list(self.request,
instance_id)
# Sort by device name
instance.volumes.sort(key=lambda vol: vol.device)
instance.full_flavor = api.flavor_get(self.request,

View File

@ -29,6 +29,8 @@ from horizon import forms
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.usage import quotas
LOG = logging.getLogger(__name__)
@ -116,7 +118,7 @@ class VolumeOptionsAction(workflows.Action):
def populate_volume_id_choices(self, request, context):
volume_options = [("", _("Select Volume"))]
try:
volumes = [v for v in api.nova.volume_list(self.request)
volumes = [v for v in cinder.volume_list(self.request)
if v.status == api.VOLUME_STATE_AVAILABLE]
volume_options.extend([self._get_volume_display_name(vol)
for vol in volumes])
@ -128,7 +130,7 @@ class VolumeOptionsAction(workflows.Action):
def populate_volume_snapshot_id_choices(self, request, context):
volume_options = [("", _("Select Volume Snapshot"))]
try:
snapshots = api.nova.volume_snapshot_list(self.request)
snapshots = cinder.volume_snapshot_list(self.request)
snapshots = [s for s in snapshots
if s.status == api.VOLUME_STATE_AVAILABLE]
volume_options.extend([self._get_volume_display_name(snap)
@ -294,7 +296,7 @@ class SetInstanceDetailsAction(workflows.Action):
def get_help_text(self):
extra = {}
try:
extra['usages'] = api.nova.tenant_quota_usages(self.request)
extra['usages'] = quotas.tenant_quota_usages(self.request)
extra['usages_json'] = json.dumps(extra['usages'])
flavors = json.dumps([f._info for f in
api.nova.flavor_list(self.request)])

View File

@ -27,8 +27,9 @@ from django.utils import timezone
from mox import IsA, Func
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard import usage
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:overview:index')
@ -37,15 +38,15 @@ INDEX_URL = reverse('horizon:project:overview:index')
class UsageViewTests(test.TestCase):
def test_usage(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
quotas = self.quota_usages.first()
usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
api.usage_get(IsA(http.HttpRequest), self.tenant.id,
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \
.AndReturn(usage_obj)
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index'))
@ -69,17 +70,17 @@ class UsageViewTests(test.TestCase):
def test_usage_csv(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
quotas = self.quota_usages.first()
usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.usage_get(IsA(http.HttpRequest),
self.tenant.id,
timestamp,
Func(usage.almost_now)) \
.AndReturn(usage_obj)
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index') +
@ -89,16 +90,16 @@ class UsageViewTests(test.TestCase):
def test_usage_exception_usage(self):
now = timezone.now()
quotas = self.quota_usages.first()
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.usage_get(IsA(http.HttpRequest),
self.tenant.id,
timestamp,
Func(usage.almost_now)) \
.AndRaise(self.exceptions.nova)
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index'))
@ -107,16 +108,16 @@ class UsageViewTests(test.TestCase):
def test_usage_exception_quota(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
usage_obj = api.nova.NovaUsage(self.usages.first())
self.mox.StubOutWithMock(api, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.usage_get(IsA(http.HttpRequest),
self.tenant.id,
timestamp,
Func(usage.almost_now)) \
.AndReturn(usage_obj)
api.nova.tenant_quota_usages(IsA(http.HttpRequest))\
quotas.tenant_quota_usages(IsA(http.HttpRequest))\
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
@ -126,17 +127,17 @@ class UsageViewTests(test.TestCase):
def test_usage_default_tenant(self):
now = timezone.now()
usage_obj = api.nova.Usage(self.usages.first())
quotas = self.quota_usages.first()
usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.usage_get(IsA(http.HttpRequest),
self.tenant.id,
timestamp,
Func(usage.almost_now)) \
.AndReturn(usage_obj)
api.nova.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quotas)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index'))

View File

@ -19,6 +19,8 @@ from horizon.utils.fields import SelectWidget
from horizon.utils.memoized import memoized
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.usage import quotas
from ..instances.tables import ACTIVE_STATES
@ -71,7 +73,7 @@ class CreateForm(forms.SelfHandlingForm):
# error message when the quota is exceeded when trying to create
# a volume, so we need to check for that scenario here before we
# send it off to try and create.
usages = api.tenant_quota_usages(request)
usages = quotas.tenant_quota_usages(request)
snapshot_id = None
if (data.get("snapshot_source", None)):
@ -116,7 +118,7 @@ class CreateForm(forms.SelfHandlingForm):
@memoized
def get_snapshot(self, request, id):
return api.nova.volume_snapshot_get(request, id)
return cinder.volume_snapshot_get(request, id)
class AttachForm(forms.SelfHandlingForm):
@ -170,10 +172,10 @@ class AttachForm(forms.SelfHandlingForm):
# it, so let's slice that off...
instance_name = instance_name.rsplit(" (")[0]
try:
vol = api.volume_attach(request,
data['volume_id'],
data['instance'],
data.get('device', ''))
vol = api.instance_volume_attach(request,
data['volume_id'],
data['instance'],
data.get('device', ''))
vol_name = api.volume_get(request, data['volume_id']).display_name
message = _('Attaching volume %(vol)s to instance '

View File

@ -185,7 +185,9 @@ class DetachVolume(tables.BatchAction):
def action(self, request, obj_id):
attachment = self.table.get_object_by_id(obj_id)
api.volume_detach(request, attachment.get('server_id', None), obj_id)
api.instance_volume_detach(request,
attachment.get('server_id', None),
obj_id)
def get_success_url(self, request):
return reverse('horizon:project:volumes:index')

View File

@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.api import cinder, nova
class OverviewTab(tabs.Tab):
@ -32,10 +32,9 @@ class OverviewTab(tabs.Tab):
def get_context_data(self, request):
volume_id = self.tab_group.kwargs['volume_id']
try:
volume = api.nova.volume_get(request, volume_id)
volume = cinder.volume_get(request, volume_id)
for att in volume.attachments:
att['instance'] = api.nova.server_get(request,
att['server_id'])
att['instance'] = nova.server_get(request, att['server_id'])
except:
redirect = reverse('horizon:project:volumes:index')
exceptions.handle(self.request,

View File

@ -26,12 +26,15 @@ from django.forms import widgets
from mox import IsA
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
class VolumeViewTests(test.TestCase):
@test.create_stubs({api: ('tenant_quota_usages', 'volume_create',
'volume_snapshot_list')})
@test.create_stubs({api: ('volume_create',
'volume_snapshot_list'),
quotas: ('tenant_quota_usages',)})
def test_create_volume(self):
volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
@ -40,7 +43,7 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 50, 'snapshot_source': ''}
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
api.volume_create(IsA(http.HttpRequest),
@ -57,9 +60,10 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({api: ('tenant_quota_usages', 'volume_create',
@test.create_stubs({api: ('volume_create',
'volume_snapshot_list'),
api.nova: ('volume_snapshot_get',)})
cinder: ('volume_snapshot_get',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_snapshot(self):
volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
@ -70,9 +74,9 @@ class VolumeViewTests(test.TestCase):
'size': 50, 'snapshot_source': snapshot.id}
# first call- with url param
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.nova.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
api.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -80,11 +84,11 @@ class VolumeViewTests(test.TestCase):
snapshot_id=snapshot.id).\
AndReturn(volume)
# second call- with dropdown
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
api.nova.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
api.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
@ -110,8 +114,8 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({api: ('tenant_quota_usages',),
api.nova: ('volume_snapshot_get',)})
@test.create_stubs({cinder: ('volume_snapshot_get',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_snapshot_invalid_size(self):
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
snapshot = self.volume_snapshots.first()
@ -120,10 +124,10 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 20, 'snapshot_source': snapshot.id}
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.nova.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@ -136,7 +140,8 @@ class VolumeViewTests(test.TestCase):
"The volume size cannot be less than the "
"snapshot size (40GB)")
@test.create_stubs({api: ('tenant_quota_usages', 'volume_snapshot_list')})
@test.create_stubs({api: ('volume_snapshot_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_gb_used_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20}}
formData = {'name': u'This Volume Is Huge!',
@ -144,10 +149,10 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 5000}
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@ -158,7 +163,8 @@ class VolumeViewTests(test.TestCase):
' have 100GB of your quota available.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
@test.create_stubs({api: ('tenant_quota_usages', 'volume_snapshot_list')})
@test.create_stubs({api: ('volume_snapshot_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_number_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20},
'volumes': {'available': 0}}
@ -167,10 +173,10 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 10}
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
api.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@ -297,14 +303,15 @@ class VolumeViewTests(test.TestCase):
server.id)
self.assertEqual(res.status_code, 200)
@test.create_stubs({api.nova: ('volume_get', 'server_get',)})
@test.create_stubs({cinder: ('volume_get',),
api.nova: ('server_get',)})
def test_detail_view(self):
volume = self.volumes.first()
server = self.servers.first()
volume.attachments = [{"server_id": server.id}]
api.nova.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()

View File

@ -30,6 +30,7 @@ from horizon import tables
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.usage import quotas
from .forms import CreateForm, AttachForm, CreateSnapshotForm
from .tables import AttachmentsTable, VolumesTable
from .tabs import VolumeDetailTabs
@ -93,7 +94,7 @@ class CreateView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
try:
context['usages'] = api.tenant_quota_usages(self.request)
context['usages'] = quotas.tenant_quota_usages(self.request)
except:
exceptions.handle(self.request)
return context

View File

@ -96,7 +96,7 @@ class ComputeApiTests(test.APITestCase):
self.mox.ReplayAll()
ret_val = api.usage_get(self.request, self.tenant.id, 'start', 'end')
self.assertIsInstance(ret_val, api.nova.Usage)
self.assertIsInstance(ret_val, api.nova.NovaUsage)
def test_usage_list(self):
usages = self.usages.list()
@ -108,7 +108,7 @@ class ComputeApiTests(test.APITestCase):
ret_val = api.usage_list(self.request, 'start', 'end')
for usage in ret_val:
self.assertIsInstance(usage, api.Usage)
self.assertIsInstance(usage, api.NovaUsage)
def test_server_get(self):
server = self.servers.first()
@ -156,51 +156,3 @@ class ComputeApiTests(test.APITestCase):
server.id,
floating_ip.id)
self.assertIsInstance(server, api.nova.Server)
@test.create_stubs({api.nova: ('volume_list',
'server_list',
'flavor_list',
'tenant_floating_ip_list',
'tenant_quota_get',)})
def test_tenant_quota_usages(self):
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn(self.servers.list())
api.nova.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
self.mox.ReplayAll()
quota_usages = api.tenant_quota_usages(self.request)
expected_output = {'gigabytes': {
'used': 80,
'flavor_fields': [],
'quota': 1000},
'ram': {
'available': 8976,
'used': 1024,
'flavor_fields': ['ram'],
'quota': 10000},
'floating_ips': {
'used': 2,
'flavor_fields': [],
'quota': 1},
'instances': {
'used': 2,
'flavor_fields': [],
'quota': 10},
'volumes': {
'used': 3,
'flavor_fields': [],
'quota': 1},
'cores': {
'used': 2,
'flavor_fields': ['vcpus'],
'quota': 10}}
self.assertEquals(quota_usages, expected_output)

View File

@ -20,6 +20,8 @@ from novaclient.v1_1 import (flavors, keypairs, servers, volumes, quotas,
security_group_rules as rules,
security_groups as sec_groups)
from openstack_dashboard.api.base import Quota, QuotaSet as QuotaSetWrapper
from openstack_dashboard.usage.quotas import QuotaUsage
from .utils import TestDataContainer
@ -265,21 +267,23 @@ def data(TEST):
injected_files='1',
cores='10')
quota = quotas.QuotaSet(quotas.QuotaSetManager(None), quota_data)
TEST.quotas.add(quota)
TEST.quotas.add(QuotaSetWrapper(quota))
# Quota Usages
TEST.quota_usages.add({'gigabytes': {'available': 1000,
'used': 0,
'quota': 1000},
'instances': {'available': 10,
'used': 0,
'quota': 10},
'ram': {'available': 10000,
'used': 0,
'quota': 10000},
'cores': {'available': 20,
'used': 0,
'quota': 20}})
quota_usage_data = {'gigabytes': {'used': 0,
'quota': 1000},
'instances': {'used': 0,
'quota': 10},
'ram': {'used': 0,
'quota': 10000},
'cores': {'used': 0,
'quota': 20}}
quota_usage = QuotaUsage()
for k, v in quota_usage_data.items():
quota_usage.add_quota(Quota(k, v['quota']))
quota_usage.tally(k, v['used'])
TEST.quota_usages.add(quota_usage)
# Servers
vals = {"host": "http://nova.example.com:8774",

View File

@ -0,0 +1,69 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
#
# 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.
from __future__ import absolute_import
from django import http
from mox import IsA
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
class QuotaTests(test.APITestCase):
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_floating_ip_list',
'tenant_quota_get',),
cinder: ('volume_list', 'tenant_quota_get',)})
def test_tenant_quota_usages(self):
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn(self.servers.list())
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = {
'injected_file_content_bytes': {'quota': 1},
'metadata_items': {'quota': 1},
'injected_files': {'quota': 1},
'gigabytes': {'available': 920, 'used': 80, 'quota': 1000},
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
'floating_ips': {'available': 0, 'used': 2, 'quota': 1},
'instances': {'available': 8, 'used': 2, 'quota': 10},
'volumes': {'available': 0, 'used': 3, 'quota': 1},
'cores': {'available': 8, 'used': 2, 'quota': 10}
}
# Compare internal structure of usages to expected.
self.assertEquals(quota_usages.usages, expected_output)

View File

@ -12,6 +12,7 @@ from horizon import forms
from horizon import messages
from openstack_dashboard import api
from openstack_dashboard.usage import quotas
LOG = logging.getLogger(__name__)
@ -108,7 +109,7 @@ class BaseUsage(object):
def get_quotas(self):
try:
self.quotas = api.nova.tenant_quota_usages(self.request)
self.quotas = quotas.tenant_quota_usages(self.request)
except:
exceptions.handle(self.request,
_("Unable to retrieve quota information."))

View File

@ -0,0 +1,108 @@
from collections import defaultdict
import itertools
from horizon import exceptions
from horizon.utils.memoized import memoized
from openstack_dashboard.api import nova, cinder
from openstack_dashboard.api.base import is_service_enabled, QuotaSet
class QuotaUsage(dict):
""" Tracks quota limit, used, and available for a given set of quotas."""
def __init__(self):
self.usages = defaultdict(dict)
def __getitem__(self, key):
return self.usages[key]
def __setitem__(self, key, value):
raise NotImplemented("Directly setting QuotaUsage values is not "
"supported. Please use the add_quota and "
"tally methods.")
def __repr__(self):
return repr(dict(self.usages))
def add_quota(self, quota):
""" Adds an internal tracking reference for the given quota. """
if quota.limit is None:
# Handle "unlimited" quotas.
self.usages[quota.name]['quota'] = float("inf")
self.usages[quota.name]['available'] = float("inf")
else:
self.usages[quota.name]['quota'] = int(quota.limit)
def tally(self, name, value):
""" Adds to the "used" metric for the given quota. """
value = value or 0 # Protection against None.
# Start at 0 if this is the first value.
if 'used' not in self.usages[name]:
self.usages[name]['used'] = 0
# Increment our usage and update the "available" metric.
self.usages[name]['used'] += int(value) # Fail if can't coerce to int.
self.update_available(name)
def update_available(self, name):
""" Updates the "available" metric for the given quota. """
available = self.usages[name]['quota'] - self.usages[name]['used']
if available < 0:
available = 0
self.usages[name]['available'] = available
def get_quota_data(request, method_name):
quotasets = []
tenant_id = request.user.tenant_id
quotasets.append(getattr(nova, method_name)(request, tenant_id))
if is_service_enabled(request, 'volume'):
quotasets.append(getattr(cinder, method_name)(request, tenant_id))
qs = QuotaSet()
for quota in itertools.chain(*quotasets):
qs[quota.name] = quota.limit
return qs
def get_default_quota_data(request):
return get_quota_data(request, "default_quota_get")
def get_tenant_quota_data(request):
return get_quota_data(request, "tenant_quota_get")
@memoized
def tenant_quota_usages(request):
# Get our quotas and construct our usage object.
usages = QuotaUsage()
for quota in get_tenant_quota_data(request):
usages.add_quota(quota)
# Get our usages.
floating_ips = nova.tenant_floating_ip_list(request)
flavors = dict([(f.id, f) for f in nova.flavor_list(request)])
volumes = cinder.volume_list(request)
instances = nova.server_list(request)
# Fetch deleted flavors if necessary.
missing_flavors = [instance.flavor['id'] for instance in instances
if instance.flavor['id'] not in flavors]
for missing in missing_flavors:
if missing not in flavors:
try:
flavors[missing] = nova.flavor_get(request, missing)
except:
flavors[missing] = {}
exceptions.handle(request, ignore=True)
usages.tally('instances', len(instances))
usages.tally('floating_ips', len(floating_ips))
usages.tally('volumes', len(volumes))
usages.tally('gigabytes', sum([int(v.size) for v in volumes]))
# Sum our usage based on the flavors of the instances.
for flavor in [flavors[instance.flavor['id']] for instance in instances]:
usages.tally('cores', getattr(flavor, 'vcpus', None))
usages.tally('ram', getattr(flavor, 'ram', None))
return usages