diff --git a/.gitignore b/.gitignore
index 45f547d88..3016667c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@
*.swp
.environment_version
.selenium_log
-.coverage
+.coverage*
.noseids
coverage.xml
pep8.txt
diff --git a/horizon/horizon/api/nova.py b/horizon/horizon/api/nova.py
index f1b7d4719..338ec539b 100644
--- a/horizon/horizon/api/nova.py
+++ b/horizon/horizon/api/nova.py
@@ -130,24 +130,40 @@ class Usage(APIResourceWrapper):
'total_local_gb_usage', 'total_memory_mb_usage',
'total_vcpus_usage', 'total_hours']
+ def get_summary(self):
+ return {'instances': self.total_active_instances,
+ 'memory_mb': self.memory_mb,
+ 'vcpus': getattr(self, "total_vcpus_usage", 0),
+ 'vcpu_hours': self.vcpu_hours,
+ 'local_gb': self.local_gb,
+ 'disk_gb_hours': self.disk_gb_hours}
+
@property
def total_active_instances(self):
return sum(1 for s in self.server_usages if s['ended_at'] == None)
@property
- def total_active_vcpus(self):
- return sum(s['vcpus']\
- for s in self.server_usages if s['ended_at'] == None)
+ def vcpus(self):
+ return sum(s['vcpus'] for s in self.server_usages
+ if s['ended_at'] == None)
@property
- def total_active_local_gb(self):
- return sum(s['local_gb']\
- for s in self.server_usages if s['ended_at'] == None)
+ def vcpu_hours(self):
+ return getattr(self, "total_hours", 0)
@property
- def total_active_memory_mb(self):
- return sum(s['memory_mb']\
- for s in self.server_usages if s['ended_at'] == None)
+ def local_gb(self):
+ return sum(s['local_gb'] for s in self.server_usages
+ if s['ended_at'] == None)
+
+ @property
+ def memory_mb(self):
+ return sum(s['memory_mb'] for s in self.server_usages
+ if s['ended_at'] == None)
+
+ @property
+ def disk_gb_hours(self):
+ return getattr(self, "total_local_gb_usage", 0)
class SecurityGroup(APIResourceWrapper):
diff --git a/horizon/horizon/dashboards/nova/overview/tests.py b/horizon/horizon/dashboards/nova/overview/tests.py
index 2901c7b86..99c4b7229 100644
--- a/horizon/horizon/dashboards/nova/overview/tests.py
+++ b/horizon/horizon/dashboards/nova/overview/tests.py
@@ -24,44 +24,74 @@ from django import http
from django.core.urlresolvers import reverse
from mox import IsA
from novaclient import exceptions as nova_exceptions
+from novaclient.v1_1 import usage as nova_usage
from horizon import api
from horizon import test
+from horizon import usage
INDEX_URL = reverse('horizon:nova:overview:index')
+USAGE_DATA = {
+ 'total_memory_mb_usage': 64246.89777777778,
+ 'total_vcpus_usage': 125.48222222222223,
+ 'total_hours': 125.48222222222223,
+ 'total_local_gb_usage': 0.0,
+ 'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
+ 'stop': u'2012-01-3123: 30: 46',
+ 'start': u'2012-01-0100: 00: 00',
+ 'server_usages': [
+ {
+ u'memory_mb': 512,
+ u'uptime': 442321,
+ u'started_at': u'2012-01-2620: 38: 21',
+ u'ended_at': None,
+ u'name': u'testing',
+ u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
+ u'state': u'active',
+ u'hours': 122.87361111111112,
+ u'vcpus': 1,
+ u'flavor': u'm1.tiny',
+ u'local_gb': 0
+ },
+ {
+ u'memory_mb': 512,
+ u'uptime': 9367,
+ u'started_at': u'2012-01-3120: 54: 15',
+ u'ended_at': None,
+ u'name': u'instance2',
+ u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
+ u'state': u'active',
+ u'hours': 2.608611111111111,
+ u'vcpus': 1,
+ u'flavor': u'm1.tiny',
+ u'local_gb': 0
+ }
+ ]
+}
-class InstanceViewTests(test.BaseViewTests):
+class UsageViewTests(test.BaseViewTests):
def setUp(self):
- super(InstanceViewTests, self).setUp()
- self.now = self.override_times()
-
- server = api.Server(None, self.request)
- server.id = "1"
- server.name = 'serverName'
- server.status = "ACTIVE"
-
- volume = api.Volume(self.request)
- volume.id = "1"
-
- self.servers = (server,)
- self.volumes = (volume,)
+ super(UsageViewTests, self).setUp()
+ usage_resource = nova_usage.Usage(nova_usage.UsageManager, USAGE_DATA)
+ self.usage = api.nova.Usage(usage_resource)
+ self.usages = (self.usage,)
def tearDown(self):
- super(InstanceViewTests, self).tearDown()
+ super(UsageViewTests, self).tearDown()
self.reset_times()
def test_usage(self):
- TEST_RETURN = 'testReturn'
-
now = self.override_times()
self.mox.StubOutWithMock(api, 'usage_get')
api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT,
datetime.datetime(now.year, now.month, 1,
now.hour, now.minute, now.second),
- now).AndReturn(TEST_RETURN)
+ datetime.datetime(now.year, now.month, now.day, now.hour,
+ now.minute, now.second)) \
+ .AndReturn(self.usage)
self.mox.ReplayAll()
@@ -69,19 +99,21 @@ class InstanceViewTests(test.BaseViewTests):
self.assertTemplateUsed(res, 'nova/overview/usage.html')
- self.assertEqual(res.context['usage'], TEST_RETURN)
+ self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
def test_usage_csv(self):
- TEST_RETURN = 'testReturn'
+ now = self.override_times()
self.mox.StubOutWithMock(api, 'usage_get')
- timestamp = datetime.datetime(self.now.year, self.now.month, 1,
- self.now.hour, self.now.minute,
- self.now.second)
+ timestamp = datetime.datetime(now.year, now.month, 1,
+ now.hour, now.minute,
+ now.second)
api.usage_get(IsA(http.HttpRequest),
self.TEST_TENANT,
timestamp,
- self.now).AndReturn(TEST_RETURN)
+ datetime.datetime(now.year, now.month, now.day, now.hour,
+ now.minute, now.second)) \
+ .AndReturn(self.usage)
self.mox.ReplayAll()
@@ -90,42 +122,46 @@ class InstanceViewTests(test.BaseViewTests):
self.assertTemplateUsed(res, 'nova/overview/usage.csv')
- self.assertEqual(res.context['usage'], TEST_RETURN)
+ self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
def test_usage_exception(self):
- self.mox.StubOutWithMock(api, 'usage_get')
+ now = self.override_times()
- timestamp = datetime.datetime(self.now.year, self.now.month, 1,
- self.now.hour, self.now.minute,
- self.now.second)
+ self.mox.StubOutWithMock(api, 'usage_get')
+ timestamp = datetime.datetime(now.year, now.month, 1, now.hour,
+ now.minute, now.second)
exception = nova_exceptions.ClientException(500)
api.usage_get(IsA(http.HttpRequest),
self.TEST_TENANT,
timestamp,
- self.now).AndRaise(exception)
+ datetime.datetime(now.year, now.month, now.day, now.hour,
+ now.minute, now.second)) \
+ .AndRaise(exception)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:overview:index'))
self.assertTemplateUsed(res, 'nova/overview/usage.html')
- self.assertEqual(res.context['usage']._apiresource, None)
+ self.assertEqual(res.context['usage'].usage_list, [])
def test_usage_default_tenant(self):
- TEST_RETURN = 'testReturn'
+ now = self.override_times()
self.mox.StubOutWithMock(api, 'usage_get')
- timestamp = datetime.datetime(self.now.year, self.now.month, 1,
- self.now.hour, self.now.minute,
- self.now.second)
+ timestamp = datetime.datetime(now.year, now.month, 1,
+ now.hour, now.minute,
+ now.second)
api.usage_get(IsA(http.HttpRequest),
self.TEST_TENANT,
timestamp,
- self.now).AndReturn(TEST_RETURN)
+ datetime.datetime(now.year, now.month, now.day, now.hour,
+ now.minute, now.second)) \
+ .AndReturn(self.usage)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:overview:index'))
self.assertTemplateUsed(res, 'nova/overview/usage.html')
- self.assertEqual(res.context['usage'], TEST_RETURN)
+ self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
diff --git a/horizon/horizon/dashboards/nova/overview/urls.py b/horizon/horizon/dashboards/nova/overview/urls.py
index f168b7933..ed61cf18e 100644
--- a/horizon/horizon/dashboards/nova/overview/urls.py
+++ b/horizon/horizon/dashboards/nova/overview/urls.py
@@ -21,6 +21,8 @@
from django.conf.urls.defaults import *
+from .views import ProjectOverview
+
urlpatterns = patterns('horizon.dashboards.nova.overview.views',
- url(r'^$', 'usage', name='index'),
+ url(r'^$', ProjectOverview.as_view(), name='index'),
)
diff --git a/horizon/horizon/dashboards/nova/overview/views.py b/horizon/horizon/dashboards/nova/overview/views.py
index 53c1376b2..787b8c3d7 100644
--- a/horizon/horizon/dashboards/nova/overview/views.py
+++ b/horizon/horizon/dashboards/nova/overview/views.py
@@ -18,75 +18,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-from __future__ import division
-
-import datetime
-import logging
-
-from django import shortcuts
-from django.utils.translation import ugettext as _
-
-import horizon
-from horizon import api
-from horizon import exceptions
-from horizon import time
+from horizon import usage
-LOG = logging.getLogger(__name__)
+class ProjectOverview(usage.UsageView):
+ table_class = usage.TenantUsageTable
+ usage_class = usage.TenantUsage
+ template_name = 'nova/overview/usage.html'
-
-def usage(request, tenant_id=None):
- tenant_id = tenant_id or request.user.tenant_id
- today = time.today()
- date_start = datetime.date(today.year, today.month, 1)
- datetime_start = datetime.datetime.combine(date_start, time.time())
- datetime_end = time.utcnow()
-
- show_terminated = request.GET.get('show_terminated', False)
-
- try:
- usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
- except:
- usage = api.nova.Usage(None)
- exceptions.handle(request,
- _('Unable to retrieve usage information.'))
-
- total_ram = 0
- ram_unit = "MB"
-
- instances = []
- terminated = []
- if hasattr(usage, 'server_usages'):
- total_ram = usage.total_active_memory_mb
- now = datetime.datetime.now()
- for i in usage.server_usages:
- i['uptime_at'] = now - datetime.timedelta(seconds=i['uptime'])
- if i['ended_at'] and not show_terminated:
- terminated.append(i)
- else:
- instances.append(i)
-
- if total_ram >= 1024:
- ram_unit = "GB"
- total_ram /= 1024
-
- if request.GET.get('format', 'html') == 'csv':
- template = 'nova/overview/usage.csv'
- mimetype = "text/csv"
- else:
- template = 'nova/overview/usage.html'
- mimetype = "text/html"
-
- dash_url = horizon.get_dashboard('nova').get_absolute_url()
-
- return shortcuts.render(request, template, {
- 'usage': usage,
- 'ram_unit': ram_unit,
- 'total_ram': total_ram,
- 'csv_link': '?format=csv',
- 'show_terminated': show_terminated,
- 'datetime_start': datetime_start,
- 'datetime_end': datetime_end,
- 'instances': instances,
- 'dash_url': dash_url},
- content_type=mimetype)
+ def get_data(self):
+ super(ProjectOverview, self).get_data()
+ return self.usage.get_instances()
diff --git a/horizon/horizon/dashboards/nova/templates/nova/overview/usage.csv b/horizon/horizon/dashboards/nova/templates/nova/overview/usage.csv
index 450f9e022..3a9aa057d 100644
--- a/horizon/horizon/dashboards/nova/templates/nova/overview/usage.csv
+++ b/horizon/horizon/dashboards/nova/templates/nova/overview/usage.csv
@@ -1,11 +1,11 @@
-Usage Report For Period:,{{datetime_start|date:"b. d Y H:i"}},/,{{datetime_end|date:"b. d Y H:i"}}
-Tenant ID:,{{usage.tenant_id}}
-Total Active VCPUs:,{{usage.total_active_vcpus}}
-CPU-HRs Used:,{{usage.total_vcpus_usage}}
-Total Active Ram (MB):,{{usage.total_active_memory_mb}}
-Total Disk Size:,{{usage.total_active_local_gb}}
-Total Disk Usage:,{{usage.total_local_gb_usage}}
+Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
+Tenant ID:,{{ usage.tenant_id }}
+Total Active VCPUs:,{{ usage.summary.instances }}
+CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
+Total Active Ram (MB):,{{ usage.summary.memory_mb }}
+Total Disk Size:,{{ usage.summary.local_gb }}
+Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
-ID,Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
-{% for server_usage in usage.server_usages %}{{server_usage.id}},{{server_usage.name|addslashes}},{{server_usage.vcpus|addslashes}},{{server_usage.memory_mb|addslashes}},{{server_usage.local_gb|addslashes}},{{server_usage.hours}},{{server_usage.uptime}},{{server_usage.state|capfirst|addslashes}}
+Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
+{% for s in usage.get_instances %}{{ s.name|addslashes }},{{ s.vcpus|addslashes }},{{ s.memory_mb|addslashes }},{{ s.local_gb|addslashes }},{{ s.hours }},{{ s.uptime }},{{ s.state|capfirst|addslashes }}
{% endfor %}
diff --git a/horizon/horizon/dashboards/nova/templates/nova/overview/usage.html b/horizon/horizon/dashboards/nova/templates/nova/overview/usage.html
index 2f0fb73e1..b100f9f32 100644
--- a/horizon/horizon/dashboards/nova/templates/nova/overview/usage.html
+++ b/horizon/horizon/dashboards/nova/templates/nova/overview/usage.html
@@ -7,72 +7,6 @@
{% endblock page_header %}
{% block dash_main %}
-
- {% if usage.server_usages %}
-
-
-
CPU
-
- - {{ usage.total_active_vcpus|default:0 }}Cores Active
- - {{ usage.total_vcpus_usage|floatformat|default:0 }}CPU-HR Used
-
-
-
-
-
RAM
-
- - {{ total_ram|default:0 }}{{ ram_unit }} Active
-
-
-
-
-
Disk
-
- - {{ usage.total_active_local_gb|default:0 }}GB Active
- - {{ usage.total_local_gb|floatformat|default:0 }}GB-HR Used
-
-
-
-
-
-
-
-
- {% trans "Name" %} |
- {% trans "Size" %} |
- {% trans "Uptime" %} |
- {% trans "State" %} |
-
-
- {% for instance in instances %}
- {% if instance.ended_at %}
-
- {% else %}
-
- {% endif %}
- {{ instance.name }} |
- {{ instance.memory_mb|mbformat }} Ram | {{ instance.vcpus }} VCPU | {{ instance.local_gb }}GB Disk |
- {{ instance.uptime_at|timesince }} |
- {{ instance.state|lower|capfirst }} |
-
- {% empty %}
-
- {% trans "No active instances." %} |
-
- {% endfor %}
-
-
- {% else %}
- {% include 'nova/instances_and_volumes/instances/_no_instances.html' %}
- {% endif %}
+ {% include "horizon/common/_usage_summary.html" %}
+ {{ table.render }}
{% endblock %}
diff --git a/horizon/horizon/dashboards/syspanel/overview/urls.py b/horizon/horizon/dashboards/syspanel/overview/urls.py
index 36f3523f4..382cd69ab 100644
--- a/horizon/horizon/dashboards/syspanel/overview/urls.py
+++ b/horizon/horizon/dashboards/syspanel/overview/urls.py
@@ -21,6 +21,8 @@
from django.conf.urls.defaults import *
-urlpatterns = patterns('horizon.dashboards.syspanel.overview.views',
- url(r'^$', 'usage', name='index'),
+from .views import GlobalOverview
+
+urlpatterns = patterns('',
+ url(r'^$', GlobalOverview.as_view(), name='index'),
)
diff --git a/horizon/horizon/dashboards/syspanel/overview/views.py b/horizon/horizon/dashboards/syspanel/overview/views.py
index d7b513337..f35bbf31d 100644
--- a/horizon/horizon/dashboards/syspanel/overview/views.py
+++ b/horizon/horizon/dashboards/syspanel/overview/views.py
@@ -18,108 +18,17 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
-import logging
-
-from dateutil.relativedelta import relativedelta
-from django import shortcuts
from django.conf import settings
-from django.contrib import messages
-from django.utils.translation import ugettext as _
-from horizon import api
-from horizon import forms
-from horizon import exceptions
+from horizon import usage
-LOG = logging.getLogger(__name__)
+class GlobalOverview(usage.UsageView):
+ table_class = usage.GlobalUsageTable
+ usage_class = usage.GlobalUsage
+ template_name = 'syspanel/overview/usage.html'
-
-class GlobalSummary(object):
- def __init__(self, request):
- self.summary = {}
- self.request = request
- self.usage_list = []
-
- def usage(self, start, end):
- try:
- self.usage_list = api.usage_list(self.request, start, end)
- except:
- self.usage_list = []
- exceptions.handle(self.request,
- _('Unable to retrieve usage information on date'
- 'range %(start)s to %(end)s' % {"start": start,
- "end": end}))
-
- # List of attrs on the Usage object that we would like to summarize
- attrs = ['total_local_gb_usage', 'total_memory_mb_usage',
- 'total_active_memory_mb', 'total_vcpus_usage',
- 'total_active_instances']
-
- for attr in attrs:
- for usage in self.usage_list:
- self.summary.setdefault(attr, 0)
- self.summary[attr] += getattr(usage, attr)
-
- @staticmethod
- def next_month(date_start):
- return date_start + relativedelta(months=1)
-
- @staticmethod
- def current_month():
- today = datetime.date.today()
- return datetime.date(today.year, today.month, 1)
-
- @staticmethod
- def get_start_and_end_date(year, month, day=1):
- date_start = datetime.date(year, month, day)
- date_end = GlobalSummary.next_month(date_start)
- datetime_start = datetime.datetime.combine(date_start, datetime.time())
- datetime_end = datetime.datetime.combine(date_end, datetime.time())
-
- if date_end > datetime.date.today():
- datetime_end = datetime.datetime.utcnow()
- return date_start, date_end, datetime_start, datetime_end
-
- @staticmethod
- def csv_link(date_start):
- return "?date_month=%s&date_year=%s&format=csv" % (date_start.month,
- date_start.year)
-
-
-def usage(request):
- today = datetime.date.today()
- dateform = forms.DateForm(request.GET, initial={'year': today.year,
- "month": today.month})
- if dateform.is_valid():
- req_year = int(dateform.cleaned_data['year'])
- req_month = int(dateform.cleaned_data['month'])
- else:
- req_year = today.year
- req_month = today.month
- date_start, date_end, datetime_start, datetime_end = \
- GlobalSummary.get_start_and_end_date(req_year, req_month)
-
- global_summary = GlobalSummary(request)
- if date_start > GlobalSummary.current_month():
- messages.error(request, _('No data for the selected period'))
- datetime_end = datetime_start
- else:
- global_summary.usage(datetime_start, datetime_end)
-
- if request.GET.get('format', 'html') == 'csv':
- template = 'syspanel/tenants/usage.csv'
- mimetype = "text/csv"
- else:
- template = 'syspanel/tenants/global_usage.html'
- mimetype = "text/html"
-
- context = {'dateform': dateform,
- 'datetime_start': datetime_start,
- 'datetime_end': datetime_end,
- 'usage_list': global_summary.usage_list,
- 'csv_link': GlobalSummary.csv_link(date_start),
- 'global_summary': global_summary.summary,
- 'external_links': getattr(settings, 'EXTERNAL_MONITORING', [])}
-
- return shortcuts.render(request, template, context, content_type=mimetype)
+ def get_context_data(self, **kwargs):
+ context = super(GlobalOverview, self).get_context_data(**kwargs)
+ context['monitoring'] = getattr(settings, 'EXTERNAL_MONITORING', [])
+ return context
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.csv b/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.csv
new file mode 100644
index 000000000..c4a069bdb
--- /dev/null
+++ b/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.csv
@@ -0,0 +1,9 @@
+Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
+Active Instances:,{{ usage.summary.instances }}
+CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
+Total Active Memory (MB):,{{ usage.summary.memory_mb }}
+Total Disk Size:,{{ usage.summary.local_gb }}
+Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
+
+Tenant,VCPUs,RamMB,DiskGB,Usage(Hours)
+{% for u in usage.usage_list %}{{ u.tenant_id|addslashes }},{{ u.vcpus|addslashes }},{{ u.memory_mb|addslashes }},{{ u.local_gb|addslashes }},{{ u.vcpu_hours}}{% endfor %}
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.html b/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.html
new file mode 100644
index 000000000..43718ca5d
--- /dev/null
+++ b/horizon/horizon/dashboards/syspanel/templates/syspanel/overview/usage.html
@@ -0,0 +1,22 @@
+{% extends 'syspanel/base.html' %}
+{% load i18n sizeformat %}
+{% block title %}{% trans "Usage Overview" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title="Overview: "|add:"This page shows overall cloud usage." %}
+{% endblock page_header %}
+
+{% block main %}
+ {% if monitoring %}
+
+
{% trans "Monitoring" %}:
+
+ {% for link in monitoring %}
+ - {{ link.0 }}
+ {% endfor %}
+
+
+ {% endif %}
+ {% include "horizon/common/_usage_summary.html" %}
+ {{ table.render }}
+{% endblock %}
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/base_usage.html b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/base_usage.html
deleted file mode 100644
index 23199b11c..000000000
--- a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/base_usage.html
+++ /dev/null
@@ -1,40 +0,0 @@
-{% extends 'syspanel/base.html' %}
-{% load i18n sizeformat %}
-{% block title %}Usage Overview{% endblock %}
-
-{% block page_header %}
- {# to make searchable false, just remove it from the include statement #}
- {% include "horizon/common/_page_header.html" with title=_("System Panel Overview") %}
-{% endblock page_header %}
-
-{% block syspanel_main %}
-{% if external_links %}
-
-
{% trans "Monitoring" %}:
-
- {% for link in external_links %}
- - {{ link.0}}
- {% endfor %}
-
-
-{% endif %}
-
-
-
-
- {% trans "Active Instances" %}: {{ global_summary.total_active_instances|default:'-' }}
- {% trans "Active Memory" %}: {{ global_summary.total_active_memory_mb|mbformat|default:'-' }}
- {% trans "This month's VCPU-Hours" %}: {{ global_summary.total_vcpus_usage|floatformat|default:'-' }}
- {% trans "This month's GB-Hours" %}: {{ global_summary.total_local_gb_usage|floatformat|default:'-' }}
-
-
-{% block activity_list %}{% endblock %}
-
-{% endblock %}
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/global_usage.html b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/global_usage.html
deleted file mode 100644
index 9c7b0dd7f..000000000
--- a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/global_usage.html
+++ /dev/null
@@ -1,43 +0,0 @@
-{% extends 'syspanel/tenants/base_usage.html' %}
-{% load i18n sizeformat %}
-{% block title %}Usage Overview{% endblock %}
-{% block activity_list %}
- {% if usage_list %}
-
-
-
-
- {% trans "Tenant" %} |
- {% trans "Instances" %} |
- {% trans "VCPUs" %} |
- {% trans "Disk" %} |
- {% trans "RAM" %} |
- {% trans "VCPU CPU-Hours" %} |
- {% trans "Disk GB-Hours" %} |
-
-
-
- {% for usage in usage_list %}
-
- {{ usage.tenant_id }} |
- {{ usage.total_active_instances }} |
- {{ usage.total_active_vcpus }} |
- {{ usage.total_active_local_gb|diskgbformat }} |
- {{ usage.total_active_memory_mb|mbformat }} |
- {{ usage.total_vcpus_usage|floatformat }} |
- {{ usage.total_local_gb_usage|floatformat }} |
-
- {% endfor %}
-
-
-
-
- Server Usage Summary
- {% trans "Download CSV" %} »
- |
-
-
-
-
- {% endif %}
-{% endblock %}
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.csv b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.csv
index 3ea6f81a4..3a9aa057d 100644
--- a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.csv
+++ b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.csv
@@ -1,10 +1,11 @@
-Usage Report For Period:,{{datetime_start|date:"b. d Y H:i"}},/,{{datetime_end|date:"b. d Y H:i"}}
-Active Instances:,{{global_summary.total_active_instances|default:'-'}}
-Active Memory (MB):,{{global_summary.total_active_memory_mb|default:'-'}}
-This month's VCPU-Hours:,{{global_summary.total_vcpus_usage|floatformat|default:'-'}}
-This month's GB-Hours:,{{global_summary.total_local_gb_usage|floatformat|default:'-'}}
-This month's MemoryMB-Hours:,{{global_summary.total_memory_mb_usage|floatformat|default:'-'}}
+Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
+Tenant ID:,{{ usage.tenant_id }}
+Total Active VCPUs:,{{ usage.summary.instances }}
+CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
+Total Active Ram (MB):,{{ usage.summary.memory_mb }}
+Total Disk Size:,{{ usage.summary.local_gb }}
+Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
-Tenant,Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
-{% for usage in usage_list %}{% for server_usage in usage.server_usages %}{{server_usage.tenant_id|addslashes}},{{server_usage.name|addslashes}},{{server_usage.vcpus|addslashes}},{{server_usage.memory_mb|addslashes}},{{server_usage.local_gb|addslashes}},{{server_usage.hours}},{{server_usage.uptime}},{{server_usage.state|capfirst|addslashes}}{% endfor %}
+Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
+{% for s in usage.get_instances %}{{ s.name|addslashes }},{{ s.vcpus|addslashes }},{{ s.memory_mb|addslashes }},{{ s.local_gb|addslashes }},{{ s.hours }},{{ s.uptime }},{{ s.state|capfirst|addslashes }}
{% endfor %}
diff --git a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.html b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.html
index ad95070bb..b7bf16aaf 100644
--- a/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.html
+++ b/horizon/horizon/dashboards/syspanel/templates/syspanel/tenants/usage.html
@@ -1,39 +1,8 @@
-{% extends 'syspanel/tenants/base_usage.html' %}
+{% extends 'syspanel/base.html' %}
{% load i18n sizeformat %}
-{% block title %}Usage Overview{% endblock %}
-{% block activity_list %}
- {% if instances %}
-
+{% block title %}{% trans "Tenant Usage Overview" %}{% endblock %}
-
-
-
- {% trans "Instances" %} |
- {% trans "VCPUs" %} |
- {% trans "Disk" %} |
- {% trans "RAM" %} |
- {% trans "Hours" %} |
- {% trans "Uptime" %} |
-
-
-
- {% for instance in instances %}
-
- {{ instance.name }} |
- {{ instance.vcpus }} |
- {{ instance.local_gb|diskgbformat }} |
- {{ instance.memory_mb|mbformat }} |
- {{ instance.hours|floatformat}} |
- {{ instance.uptime_at|timesince}} |
-
- {% endfor %}
-
-
-
- Tenant Server Usage Summary.
- {% trans "Download CSV" %} »
- |
-
-
- {% endif %}
+{% block syspanel_main %}
+ {% include "horizon/common/_usage_summary.html" %}
+ {{ table.render }}
{% endblock %}
diff --git a/horizon/horizon/dashboards/syspanel/tenants/urls.py b/horizon/horizon/dashboards/syspanel/tenants/urls.py
index 18bf74807..598c7c3b9 100644
--- a/horizon/horizon/dashboards/syspanel/tenants/urls.py
+++ b/horizon/horizon/dashboards/syspanel/tenants/urls.py
@@ -21,17 +21,18 @@
from django.conf.urls.defaults import patterns, url
from .views import (IndexView, CreateView, UpdateView, QuotasView, UsersView,
- AddUserView)
+ AddUserView, TenantUsageView)
-urlpatterns = patterns('horizon.dashboards.syspanel.tenants.views',
+urlpatterns = patterns('',
url(r'^$', IndexView.as_view(), name='index'),
url(r'^create$', CreateView.as_view(), name='create'),
url(r'^(?P[^/]+)/update/$',
UpdateView.as_view(), name='update'),
url(r'^(?P[^/]+)/quotas/$',
QuotasView.as_view(), name='quotas'),
- url(r'^(?P[^/]+)/usage/$', 'usage', name='usage'),
+ url(r'^(?P[^/]+)/usage/$',
+ TenantUsageView.as_view(), name='usage'),
url(r'^(?P[^/]+)/users/$', UsersView.as_view(), name='users'),
url(r'^(?P[^/]+)/users/(?P[^/]+)/add/$',
AddUserView.as_view(), name='add_user')
diff --git a/horizon/horizon/dashboards/syspanel/tenants/views.py b/horizon/horizon/dashboards/syspanel/tenants/views.py
index 5fc4ce27e..59fdf42a2 100644
--- a/horizon/horizon/dashboards/syspanel/tenants/views.py
+++ b/horizon/horizon/dashboards/syspanel/tenants/views.py
@@ -18,11 +18,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
import logging
import operator
-from django import shortcuts
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
@@ -33,11 +31,10 @@ from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import tables
+from horizon import usage
from .forms import AddUser, CreateTenant, UpdateTenant, UpdateQuotas
from .tables import TenantsTable, TenantUsersTable, AddUsersTable
-from horizon.dashboards.syspanel.overview.views import GlobalSummary
-
LOG = logging.getLogger(__name__)
@@ -181,60 +178,11 @@ class QuotasView(forms.ModalFormView):
'cores': quotas.cores}
-def usage(request, tenant_id):
- today = datetime.date.today()
- dateform = forms.DateForm(request.GET, initial={'year': today.year,
- "month": today.month})
- if dateform.is_valid():
- req_year = int(dateform.cleaned_data['year'])
- req_month = int(dateform.cleaned_data['month'])
- else:
- req_year = today.year
- req_month = today.month
- date_start, date_end, datetime_start, datetime_end = \
- GlobalSummary.get_start_and_end_date(req_year, req_month)
+class TenantUsageView(usage.UsageView):
+ table_class = usage.TenantUsageTable
+ usage_class = usage.TenantUsage
+ template_name = 'syspanel/tenants/usage.html'
- if date_start > GlobalSummary.current_month():
- messages.error(request, _('No data for the selected period'))
- datetime_end = datetime_start
-
- usage = {}
- try:
- usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
- except api_exceptions.ApiException, e:
- LOG.exception('ApiException getting usage info for tenant "%s"'
- ' on date range "%s to %s"' % (tenant_id,
- datetime_start,
- datetime_end))
- messages.error(request, _('Unable to get usage info: %s') % e.message)
-
- running_instances = []
- terminated_instances = []
- if hasattr(usage, 'server_usages'):
- now = datetime.datetime.now()
- for i in usage.server_usages:
- # this is just a way to phrase uptime in a way that is compatible
- # with the 'timesince' filter. Use of local time intentional
- i['uptime_at'] = now - datetime.timedelta(seconds=i['uptime'])
- if i['ended_at']:
- terminated_instances.append(i)
- else:
- running_instances.append(i)
-
- if request.GET.get('format', 'html') == 'csv':
- template = 'syspanel/tenants/usage.csv'
- mimetype = "text/csv"
- else:
- template = 'syspanel/tenants/usage.html'
- mimetype = "text/html"
-
- context = {'dateform': dateform,
- 'datetime_start': datetime_start,
- 'datetime_end': datetime_end,
- 'global_summary': usage,
- 'usage_list': [usage],
- 'csv_link': GlobalSummary.csv_link(date_start),
- 'instances': running_instances + terminated_instances,
- 'tenant_id': tenant_id}
-
- return shortcuts.render(request, template, context, content_type=mimetype)
+ def get_data(self):
+ super(TenantUsageView, self).get_data()
+ return self.usage.get_instances()
diff --git a/horizon/horizon/tables/actions.py b/horizon/horizon/tables/actions.py
index 64c08d6d3..f6257e535 100644
--- a/horizon/horizon/tables/actions.py
+++ b/horizon/horizon/tables/actions.py
@@ -209,7 +209,8 @@ class LinkAction(BaseAction):
.. attribute:: url
A string or a callable which resolves to a url to be used as the link
- target. (Required)
+ target. You must either define the ``url`` attribute or a override
+ the ``get_link_url`` method on the class.
"""
method = "GET"
bound_url = None
@@ -224,9 +225,6 @@ class LinkAction(BaseAction):
if not self.verbose_name:
raise NotImplementedError('A LinkAction object must have a '
'verbose_name attribute.')
- if not self.url:
- raise NotImplementedError('A LinkAction object must have a '
- 'url attribute.')
if attrs:
self.attrs.update(attrs)
@@ -240,6 +238,10 @@ class LinkAction(BaseAction):
When called for a row action, the current row data object will be
passed as the first parameter.
"""
+ if not self.url:
+ raise NotImplementedError('A LinkAction class must have a '
+ 'url attribute or define its own '
+ 'get_link_url method.')
if callable(self.url):
return self.url(datum, **self.kwargs)
try:
diff --git a/horizon/horizon/templates/horizon/common/_usage_summary.html b/horizon/horizon/templates/horizon/common/_usage_summary.html
new file mode 100644
index 000000000..7efba15f9
--- /dev/null
+++ b/horizon/horizon/templates/horizon/common/_usage_summary.html
@@ -0,0 +1,17 @@
+{% load i18n sizeformat %}
+
+
+
+
+ {% trans "Active Instances" %}: {{ usage.summary.instances|default:'-' }}
+ {% trans "Active Memory" %}: {{ usage.summary.memory_mb|mbformat|default:'-' }}
+ {% trans "This Month's VCPU-Hours" %}: {{ usage.summary.vcpu_hours|floatformat|default:'-' }}
+ {% trans "This Month's GB-Hours" %}: {{ usage.summary.disk_gb_hours|floatformat|default:'-' }}
+
diff --git a/horizon/horizon/time.py b/horizon/horizon/time.py
index 7cd08083e..a67b56149 100644
--- a/horizon/horizon/time.py
+++ b/horizon/horizon/time.py
@@ -1,11 +1,11 @@
import datetime
-def time():
+def time(hour=0, minute=0, second=0, microsecond=0):
'''Overrideable version of datetime.datetime.today'''
if time.override_time:
return time.override_time
- return datetime.time()
+ return datetime.time(hour, minute, second, microsecond)
time.override_time = None
@@ -14,7 +14,7 @@ def today():
'''Overridable version of datetime.datetime.today'''
if today.override_time:
return today.override_time
- return datetime.datetime.today()
+ return datetime.date.today()
today.override_time = None
diff --git a/horizon/horizon/usage/__init__.py b/horizon/horizon/usage/__init__.py
new file mode 100644
index 000000000..e19fee2bf
--- /dev/null
+++ b/horizon/horizon/usage/__init__.py
@@ -0,0 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nebula, 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 .base import BaseUsage, TenantUsage, GlobalUsage
+from .views import UsageView
+from .tables import BaseUsageTable, TenantUsageTable, GlobalUsageTable
diff --git a/horizon/horizon/usage/base.py b/horizon/horizon/usage/base.py
new file mode 100644
index 000000000..c569e3686
--- /dev/null
+++ b/horizon/horizon/usage/base.py
@@ -0,0 +1,138 @@
+from __future__ import division
+
+import datetime
+import logging
+
+from dateutil.relativedelta import relativedelta
+from django.contrib import messages
+from django.utils.translation import ugettext as _
+
+from horizon import api
+from horizon import exceptions
+from horizon import forms
+from horizon import time
+
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseUsage(object):
+ show_terminated = False
+
+ def __init__(self, request, tenant_id=None):
+ self.tenant_id = tenant_id or request.user.tenant_id
+ self.request = request
+ self.summary = {}
+ self.usage_list = []
+
+ @property
+ def today(self):
+ return time.today()
+
+ @staticmethod
+ def get_datetime(date, now=False):
+ if now:
+ now = time.utcnow()
+ current_time = time.time(now.hour, now.minute, now.second)
+ else:
+ current_time = time.time()
+ return datetime.datetime.combine(date, current_time)
+
+ @staticmethod
+ def get_start(year, month, day=1):
+ return datetime.date(year, month, day)
+
+ @staticmethod
+ def get_end(year, month, day=1):
+ period = relativedelta(months=1)
+ date_end = BaseUsage.get_start(year, month, day) + period
+ if date_end > time.today():
+ date_end = time.today()
+ return date_end
+
+ def get_instances(self):
+ instance_list = []
+ [instance_list.extend(u.server_usages) for u in self.usage_list]
+ return instance_list
+
+ def get_date_range(self):
+ if not hasattr(self, "start") or not hasattr(self, "end"):
+ args = (self.today.year, self.today.month)
+ form = self.get_form()
+ if form.is_valid():
+ args = (int(form.cleaned_data['year']),
+ int(form.cleaned_data['month']))
+ self.start = self.get_start(*args)
+ self.end = self.get_end(*args)
+ return self.start, self.end
+
+ def get_form(self):
+ if not hasattr(self, 'form'):
+ self.form = forms.DateForm(self.request.GET,
+ initial={'year': self.today.year,
+ 'month': self.today.month})
+ return self.form
+
+ def get_usage_list(self, start, end):
+ raise NotImplementedError("You must define a get_usage method.")
+
+ def get_summary(self):
+ raise NotImplementedError("You must define a get_summary method.")
+
+ def summarize(self, start, end):
+ if start <= end <= time.today():
+ # Convert to datetime.datetime just for API call.
+ start = BaseUsage.get_datetime(start)
+ end = BaseUsage.get_datetime(end, now=True)
+ try:
+ self.usage_list = self.get_usage_list(start, end)
+ except:
+ exceptions.handle(self.request,
+ _('Unable to retrieve usage information.'))
+ else:
+ messages.info(self.request,
+ _("You are viewing data for the future, "
+ "which may or may not exist."))
+
+ for tenant_usage in self.usage_list:
+ tenant_summary = tenant_usage.get_summary()
+ for key, value in tenant_summary.items():
+ self.summary.setdefault(key, 0)
+ self.summary[key] += value
+
+ def csv_link(self):
+ return "?date_month=%s&date_year=%s&format=csv" % self.get_date_range()
+
+
+class GlobalUsage(BaseUsage):
+ show_terminated = True
+
+ def get_usage_list(self, start, end):
+ return api.usage_list(self.request, start, end)
+
+
+class TenantUsage(BaseUsage):
+ attrs = ('memory_mb', 'vcpus', 'uptime',
+ 'hours', 'local_gb')
+
+ def get_usage_list(self, start, end):
+ show_terminated = self.request.GET.get('show_terminated',
+ self.show_terminated)
+ instances = []
+ terminated_instances = []
+ usage = api.usage_get(self.request, self.tenant_id, start, end)
+ # Attribute may not exist if there are no instances
+ if hasattr(usage, 'server_usages'):
+ now = datetime.datetime.now()
+ for server_usage in usage.server_usages:
+ # This is a way to phrase uptime in a way that is compatible
+ # with the 'timesince' filter. (Use of local time intentional.)
+ server_uptime = server_usage['uptime']
+ total_uptime = now - datetime.timedelta(seconds=server_uptime)
+ server_usage['uptime_at'] = total_uptime
+ if server_usage['ended_at'] and not show_terminated:
+ terminated_instances.append(server_usage)
+ else:
+ instances.append(server_usage)
+ usage.server_usages = instances
+ return (usage,)
diff --git a/horizon/horizon/usage/tables.py b/horizon/horizon/usage/tables.py
new file mode 100644
index 000000000..c62761e9e
--- /dev/null
+++ b/horizon/horizon/usage/tables.py
@@ -0,0 +1,56 @@
+from django.utils.translation import ugettext as _
+from django.template.defaultfilters import timesince
+
+from horizon import tables
+from horizon.templatetags.sizeformat import mbformat
+
+
+class CSVSummary(tables.LinkAction):
+ name = "csv_summary"
+ verbose_name = _("Download CSV Summary")
+
+ def get_link_url(self, usage=None):
+ return self.table.kwargs['usage'].csv_link()
+
+
+class BaseUsageTable(tables.DataTable):
+ vcpus = tables.Column('vcpus', verbose_name=_("VCPUs"))
+ disk = tables.Column('local_gb', verbose_name=_("Disk"))
+ memory = tables.Column('memory_mb',
+ verbose_name=_("RAM"),
+ filters=(mbformat,))
+ hours = tables.Column('vcpu_hours', verbose_name=_("VCPU Hours"))
+
+
+class GlobalUsageTable(BaseUsageTable):
+ tenant = tables.Column('tenant_id', verbose_name=_("Tenant ID"))
+ disk_hours = tables.Column('disk_gb_hours',
+ verbose_name=_("Disk GB Hours"))
+
+ def get_object_id(self, datum):
+ return datum.tenant_id
+
+ class Meta:
+ name = "global_usage"
+ verbose_name = _("Usage Summary")
+ columns = ("tenant", "vcpus", "disk", "memory",
+ "hours", "disk_hours")
+ table_actions = (CSVSummary,)
+ multi_select = False
+
+
+class TenantUsageTable(BaseUsageTable):
+ instance = tables.Column('name')
+ uptime = tables.Column('uptime_at',
+ verbose_name=_("Uptime"),
+ filters=(timesince,))
+
+ def get_object_id(self, datum):
+ return datum['name']
+
+ class Meta:
+ name = "tenant_usage"
+ verbose_name = _("Usage Summary")
+ columns = ("instance", "vcpus", "disk", "memory", "uptime")
+ table_actions = (CSVSummary,)
+ multi_select = False
diff --git a/horizon/horizon/usage/views.py b/horizon/horizon/usage/views.py
new file mode 100644
index 000000000..cebdb6011
--- /dev/null
+++ b/horizon/horizon/usage/views.py
@@ -0,0 +1,52 @@
+import logging
+
+from horizon import tables
+from .base import BaseUsage
+
+
+LOG = logging.getLogger(__name__)
+
+
+class UsageView(tables.DataTableView):
+ usage_class = None
+ show_terminated = True
+
+ def __init__(self, *args, **kwargs):
+ super(UsageView, self).__init__(*args, **kwargs)
+ if not issubclass(self.usage_class, BaseUsage):
+ raise AttributeError("You must specify a usage_class attribute "
+ "which is a subclass of BaseUsage.")
+
+ def get_template_names(self):
+ if self.request.GET.get('format', 'html') == 'csv':
+ return ".".join((self.template_name.rsplit('.', 1)[0], 'csv'))
+ return self.template_name
+
+ def get_content_type(self):
+ if self.request.GET.get('format', 'html') == 'csv':
+ return "text/csv"
+ return "text/html"
+
+ def get_data(self):
+ tenant_id = self.kwargs.get('tenant_id', self.request.user.tenant_id)
+ self.usage = self.usage_class(self.request, tenant_id)
+ self.usage.summarize(*self.usage.get_date_range())
+ self.kwargs['usage'] = self.usage
+ return self.usage.usage_list
+
+ def get_context_data(self, **kwargs):
+ context = super(UsageView, self).get_context_data(**kwargs)
+ context['form'] = self.usage.form
+ context['usage'] = self.usage
+ return context
+
+ def render_to_response(self, context, **response_kwargs):
+ resp = self.response_class(request=self.request,
+ template=self.get_template_names(),
+ context=context,
+ content_type=self.get_content_type(),
+ **response_kwargs)
+ if self.request.GET.get('format', 'html') == 'csv':
+ resp['Content-Disposition'] = 'attachment; filename=usage.csv'
+ resp['Content-Type'] = 'text/csv'
+ return resp
diff --git a/openstack-dashboard/dashboard/static/dashboard/css/style.css b/openstack-dashboard/dashboard/static/dashboard/css/style.css
index 9cfa654c1..5ea499a58 100644
--- a/openstack-dashboard/dashboard/static/dashboard/css/style.css
+++ b/openstack-dashboard/dashboard/static/dashboard/css/style.css
@@ -551,10 +551,10 @@ table form {
min-width: 735px;
padding: 5px 0 5px 0;
border: 1px solid #e6e6e6;
+ margin-bottom: 25px;
clear: both;
}
-#activity.tenant { margin: 0 0 0 0; }
#activity span { margin: 0 0 0 10px; }
#activity strong {
@@ -574,10 +574,10 @@ table form {
}
#monitoring h3{
- font-size: 13px;
+ font-size: 14px;
font-weight: normal;
float: left;
- margin-top: -8px;
+ line-height: 18px;
}
#external_links, #external_links li {
diff --git a/run_tests.sh b/run_tests.sh
index f9e4cae8a..a31c7181b 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -6,7 +6,7 @@ set -o errexit
# Increment me any time the environment should be rebuilt.
# This includes dependncy changes, directory renames, etc.
# Simple integer secuence: 1, 2, 3...
-environment_version=8
+environment_version=10
#--------------------------------------------------------#
function usage {
@@ -205,6 +205,9 @@ function sanity_check {
selenium=0
fi
fi
+ # Remove .pyc files. This is sanity checking because they can linger
+ # after old files are deleted.
+ find . -name "*.pyc" -exec rm -rf {} \;
}
function backup_environment {
@@ -249,13 +252,9 @@ function install_venv {
if [ $quiet -eq 1 ]; then
export PIP_NO_INPUT=true
fi
- INSTALL_FAILED=0
- python tools/install_venv.py || INSTALL_FAILED=1
- if [ $INSTALL_FAILED -eq 1 ]; then
- echo "Error updating environment with pip, trying without src packages..."
- rm -rf $venv/src
- python tools/install_venv.py
- fi
+ echo "Fetching new src packages..."
+ rm -rf $venv/src
+ python tools/install_venv.py
command_wrapper="$root/${with_venv}"
# Make sure it worked and record the environment version
sanity_check