Commit 8a3dbb79 by Kálmán Viktor

Merge branch 'master' into feature-request

parents f157eb56 3159f5f9
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import os# noqa
from .base import * # noqa
# flake8: noqa
os.environ['REUSE_DB'] = "1"
os.environ['DJANGO_TEST_DB_NAME'] = "circle"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' +
get_env_variable('DJANG_DB_TYPE', 'postgresql_psycopg2'),
'NAME': get_env_variable('DJANGO_DB_NAME', 'circle'),
'TEST_NAME': get_env_variable('DJANGO_TEST_DB_NAME', 'circle'),
'USER': get_env_variable('DJANGO_DB_USER', 'circle'),
'PASSWORD': get_env_variable('DJANGO_DB_PASSWORD'),
'HOST': get_env_variable('DJANGO_DB_HOST', ''),
'PORT': get_env_variable('DJANGO_DB_PORT', ''),
}
}
SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
'django_jenkins',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
path_to_selenium_test = os.path.expanduser('~/circle/circle/dashboard/tests/selenium')
NOSE_ARGS = ['--stop', '--with-doctest', '--with-selenium-driver', '--selenium-driver=firefox', '-w%s' % path_to_selenium_test]
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'],
'level': 'CRITICAL'}
level = environ.get('LOGLEVEL', 'CRITICAL')
LOGGING['handlers']['console'] = {'level': level,
'class': 'logging.StreamHandler',
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
...@@ -38,7 +38,7 @@ INSTALLED_APPS += ( ...@@ -38,7 +38,7 @@ INSTALLED_APPS += (
'django_nose', 'django_nose',
) )
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--with-doctest'] NOSE_ARGS = ['--with-doctest', '--exclude-dir=dashboard/tests/selenium']
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher'] PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = { CACHES = {
......
...@@ -69,7 +69,8 @@ def activitycontextimpl(act, on_abort=None, on_commit=None): ...@@ -69,7 +69,8 @@ def activitycontextimpl(act, on_abort=None, on_commit=None):
# system-exiting exceptions, e.g. KeyboardInterrupt # system-exiting exceptions, e.g. KeyboardInterrupt
result = create_readable( result = create_readable(
ugettext_noop("Failure."), ugettext_noop("Failure."),
ugettext_noop("Unhandled exception: %(error)s"), ugettext_noop("Unhandled exception: %(e)s: %(error)s"),
e=str(e.__class__.__name__),
error=get_error_msg(e)) error=get_error_msg(e))
raise raise
except: except:
......
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from mock import patch
class MockCeleryMixin(object):
def _pre_setup(self):
self.reloadtask_patcher = patch(
'firewall.tasks.local_tasks.reloadtask.apply_async', spec=True)
self.reloadtask_patcher.start()
self.kombu_patcher = patch('kombu.connection.Connection.ensure',
side_effect=RuntimeError())
self.kombu_patcher.start()
self.check_queue_patcher = patch('vm.tasks.vm_tasks.check_queue',
return_value=True)
self.check_queue_patcher.start()
super(MockCeleryMixin, self)._pre_setup()
def _post_teardown(self):
self.reloadtask_patcher.stop()
self.kombu_patcher.stop()
super(MockCeleryMixin, self)._post_teardown()
...@@ -20,12 +20,13 @@ from collections import deque ...@@ -20,12 +20,13 @@ from collections import deque
from django.test import TestCase from django.test import TestCase
from mock import MagicMock from mock import MagicMock
from .celery_mock import MockCeleryMixin
from .models import TestClass from .models import TestClass
from ..models import HumanSortField from ..models import HumanSortField
from ..models import activitycontextimpl from ..models import activitycontextimpl
class MethodCacheTestCase(TestCase): class MethodCacheTestCase(MockCeleryMixin, TestCase):
def test_cache(self): def test_cache(self):
t1 = TestClass(1) t1 = TestClass(1)
t2 = TestClass(2) t2 = TestClass(2)
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import logging
from optparse import make_option from optparse import make_option
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -28,6 +29,9 @@ from storage.models import DataStore ...@@ -28,6 +29,9 @@ from storage.models import DataStore
from vm.models import Lease from vm.models import Lease
logger = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--force', action="store_true"), make_option('--force', action="store_true"),
...@@ -49,18 +53,18 @@ class Command(BaseCommand): ...@@ -49,18 +53,18 @@ class Command(BaseCommand):
qs = model.objects.filter(**{field: value})[:1] qs = model.objects.filter(**{field: value})[:1]
if not qs.exists(): if not qs.exists():
obj = model.objects.create(**kwargs) obj = model.objects.create(**kwargs)
self.changed.append('New %s: %s' % (model, obj)) logger.info('New %s: %s', model, obj)
self.changed = True
return obj return obj
else: else:
return qs[0] return qs[0]
# http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html # http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html
def print_state(self): def print_state(self):
changed = "yes" if len(self.changed) else "no" print "\nchanged=%s" % ("yes" if self.changed else "no")
print "\nchanged=%s comment='%s'" % (changed, ", ".join(self.changed))
def handle(self, *args, **options): def handle(self, *args, **options):
self.changed = [] self.changed = False
if (DataStore.objects.exists() and Vlan.objects.exists() if (DataStore.objects.exists() and Vlan.objects.exists()
and not options['force']): and not options['force']):
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='connectcommand',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='futuremember',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='groupprofile',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='profile',
options={'ordering': ('id',), 'permissions': (('use_autocomplete', 'Can use autocomplete.'),)},
),
]
...@@ -128,6 +128,9 @@ class ConnectCommand(Model): ...@@ -128,6 +128,9 @@ class ConnectCommand(Model):
'host, port.'), 'host, port.'),
validators=[connect_command_template_validator]) validators=[connect_command_template_validator])
class Meta:
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.template return self.template
...@@ -218,6 +221,7 @@ class Profile(Model): ...@@ -218,6 +221,7 @@ class Profile(Model):
super(Profile, self).save(*args, **kwargs) super(Profile, self).save(*args, **kwargs)
class Meta: class Meta:
ordering = ('id', )
permissions = ( permissions = (
('use_autocomplete', _('Can use autocomplete.')), ('use_autocomplete', _('Can use autocomplete.')),
) )
...@@ -229,6 +233,7 @@ class FutureMember(Model): ...@@ -229,6 +233,7 @@ class FutureMember(Model):
group = ForeignKey(Group) group = ForeignKey(Group)
class Meta: class Meta:
ordering = ('id', )
unique_together = ('org_id', 'group') unique_together = ('org_id', 'group')
def __unicode__(self): def __unicode__(self):
...@@ -247,6 +252,9 @@ class GroupProfile(AclBase): ...@@ -247,6 +252,9 @@ class GroupProfile(AclBase):
help_text=_('Unique identifier of the group at the organization.')) help_text=_('Unique identifier of the group at the organization.'))
description = TextField(blank=True) description = TextField(blank=True)
class Meta:
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.group.name return self.group.name
......
...@@ -149,6 +149,7 @@ class GroupListTable(Table): ...@@ -149,6 +149,7 @@ class GroupListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'group-list-table')} 'group-list-table')}
fields = ('pk', 'name', ) fields = ('pk', 'name', )
order_by = ('pk', )
class UserListTable(Table): class UserListTable(Table):
...@@ -176,6 +177,7 @@ class UserListTable(Table): ...@@ -176,6 +177,7 @@ class UserListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover')} attrs = {'class': ('table table-bordered table-striped table-hover')}
fields = ('username', 'last_name', 'first_name', 'profile__org_id', fields = ('username', 'last_name', 'first_name', 'profile__org_id',
'email', 'is_active', 'is_superuser') 'email', 'is_active', 'is_superuser')
order_by = ('username', )
class TemplateListTable(Table): class TemplateListTable(Table):
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
{% csrf_token %} {% csrf_token %}
<a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a> <a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a>
<a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a> <a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a>
<button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}"> <button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success modal-accept" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}">
<i class="fa fa-external-link"></i> {% trans "I have the Client installed" %} <i class="fa fa-external-link"></i> {% trans "I have the Client installed" %}
</button> </button>
<input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" /> <input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" />
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
{% csrf_token %} {% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<input type="hidden" name="next" value="{{ request.GET.next }}"/> <input type="hidden" name="next" value="{{ request.GET.next }}"/>
<button class="btn btn-danger" <button class="btn btn-danger modal-accept"
{% if disable_submit %}disabled{% endif %} {% if disable_submit %}disabled{% endif %}
>{% trans "Delete" %}</button> >{% trans "Delete" %}</button>
</form> </form>
......
...@@ -31,7 +31,7 @@ Do you want to perform the <strong>{{op}}</strong> operation on the following {{ ...@@ -31,7 +31,7 @@ Do you want to perform the <strong>{{op}}</strong> operation on the following {{
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}" <a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}"
data-dismiss="modal">{% trans "Cancel" %}</a> data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }}" type="submit" id="mass-op-form-send"> <button class="btn btn-{{ opview.effect }} modal-accept" type="submit" id="mass-op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }}
</button> </button>
</div> </div>
......
...@@ -39,3 +39,15 @@ ...@@ -39,3 +39,15 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if show_show_all %}
<div id="show-all-activities-container">
<a id="show-all-activities" href="#">
{% if activities|length > 10 %}
{% trans "Show less activities" %} <i class="fa fa-angle-double-up"></i>
{% else %}
{% trans "Show all activities" %} <i class="fa fa-angle-double-down"></i>
{% endif %}
</a>
</div>
{% endif %}
...@@ -20,7 +20,7 @@ Do you want to perform the following operation on ...@@ -20,7 +20,7 @@ Do you want to perform the following operation on
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}" <a class="btn btn-default" href="{{object.get_absolute_url}}"
data-dismiss="modal">{% trans "Cancel" %}</a> data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }} btn-op-form-send" type="submit" id="op-form-send"> <button class="btn btn-{{ opview.effect }} btn-op-form-send modal-accept" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button> </button>
</div> </div>
......
...@@ -89,10 +89,10 @@ ...@@ -89,10 +89,10 @@
<div class="input-group"> <div class="input-group">
<input type="text" name="s" class="form-control" <input type="text" name="s" class="form-control"
value="{{ request.GET.s }}" placeholder="{% trans "Search..." %}"/> value="{{ request.GET.s }}" placeholder="{% trans "Search..." %}"/>
<input type="hidden" name="filter" value="{{ request.GET.filter }}"/>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-primary"><i class="fa fa-search"></i></button> <button class="btn btn-primary"><i class="fa fa-search"></i></button>
</span> </span>
<input type="hidden" name="filter" value="{{ request.GET.filter }}"/>
</div> </div>
</form> </form>
</div> </div>
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from selenose.cases import SeleniumTestCase
from django.contrib.auth.models import User
import random
import urlparse
import re
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
from datetime import datetime
from selenium.common.exceptions import NoSuchElementException
random_pass = "".join([random.choice(
'0123456789abcdefghijklmnopqrstvwxyz') for n in xrange(10)])
random_accents = random_pass + "".join([random.choice(
u"áéíöóúűÁÉÍÖÓÜÚŰ") for n in xrange(5)])
wait_max_sec = 10
host = 'https:127.0.0.1'
client_name = 'test_%s' % random_accents
class UtilityMixin(object):
def login(self, username, password='password', location=None):
driver = self.driver
if location is None:
location = '/dashboard/'
driver.get('%s%s' % (host, location))
# Only if we aren't logged in already
if location not in urlparse.urlparse(self.driver.current_url).path:
try:
name_input = driver.find_element_by_id("id_username")
password_input = driver.find_element_by_id("id_password")
submit_input = driver.find_element_by_id("submit-id-submit")
except:
inputs = driver.find_elements_by_tag_name("input")
for current_input in inputs:
input_type = current_input.get_attribute("type")
if input_type == "text":
name_input = current_input
if input_type == "password":
password_input = current_input
if input_type == "submit":
submit_input = current_input
try:
name_input.clear()
name_input.send_keys(username)
password_input.clear()
password_input.send_keys(password)
submit_input.click()
try:
# If selenium runs only in a small (virtual) screen
driver.find_element_by_class_name('navbar-toggle').click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href*='/dashboard/profile/']")))
except:
time.sleep(0.5)
except:
raise Exception('Selenium cannot find the form controls')
def list_options(self, select):
try:
option_dic = {}
select = Select(select)
for option in select.options:
key = option.get_attribute('value')
if key is not None and key:
option_dic[key] = [option.text]
return option_dic
except:
raise Exception(
'Selenium cannot list the select possibilities')
def select_option(self, select, what=None):
"""
From an HTML select imput type try to choose the specified one.
Select is a selenium web element type. What represent both the
text of the option and it's ID.
"""
try:
my_choice = None
options = self.list_options(select)
select = Select(select)
if what is not None:
for key, value in options.iteritems():
if what in key:
my_choice = key
else:
if isinstance(value, list):
for single_value in value:
if what in single_value:
my_choice = key
else:
if what in value:
my_choice = key
if my_choice is None:
my_choose_list = options.keys()
my_choice = my_choose_list[random.randint(
0, len(my_choose_list) - 1)]
select.select_by_value(my_choice)
except:
raise Exception(
'Selenium cannot select the chosen one')
def get_link_by_href(self, target_href, attributes=None):
try:
links = self.driver.find_elements_by_tag_name('a')
for link in links:
href = link.get_attribute('href')
if href is not None and href:
if target_href in href:
perfect_fit = True
if isinstance(attributes, dict):
for key, target_value in attributes.iteritems():
attr_check = link.get_attribute(key)
if attr_check is not None and attr_check:
if target_value not in attr_check:
perfect_fit = False
if perfect_fit:
return link
except:
raise Exception(
'Selenium cannot find the href=%s link' % target_href)
def click_on_link(self, link):
"""
There are situations when selenium built in click() function
doesn't work as intended, that's when this function is used.
Fires a click event via javascript injection.
"""
try:
# Javascript function to simulate a click on a link
javascript = (
"var link = arguments[0];"
"var cancelled = false;"
"if(document.createEvent) {"
" var event = document.createEvent(\"MouseEvents\");"
" event.initMouseEvent("
" \"click\", true, true, window, 0, 0, 0, 0, 0,"
" false,false,false,false,0,null);"
" cancelled = !link.dispatchEvent(event);"
"} else if(link.fireEvent) {"
" cancelled = !link.fireEvent(\"onclick\");"
"} if (!cancelled) {"
" window.location = link.href;"
"}")
self.driver.execute_script(javascript, link)
except:
raise Exception(
'Selenium cannot inject javascript to the page')
def wait_and_accept_operation(self, argument=None):
"""
Accepts the operation confirmation pop up window.
Fills out the text inputs before accepting if argument is given.
"""
try:
accept = WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, "modal-accept")))
if argument is not None:
possible = self.driver.find_elements_by_css_selector(
"div.controls > input[type='text']")
if isinstance(argument, list):
for x in range(0, len(possible)):
possible[x].clear()
possible[x].send_keys(argument[x % len(argument)])
else:
for form in possible:
form.clear()
form.send_keys(argument)
accept.click()
except:
raise Exception("Selenium cannot accept the"
" operation confirmation")
def save_template_from_vm(self, name):
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/deploy/']")))
self.click_on_link(self.get_link_by_href("/op/deploy/"))
self.wait_and_accept_operation()
recent_deploy = self.recently(self.get_timeline_elements(
"vm.Instance.deploy"))
if not self.check_operation_result(recent_deploy):
print ("Selenium cannot deploy the "
"chosen template virtual machine")
raise Exception('Cannot deploy the virtual machine')
self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/shut_off/']"))))
self.wait_and_accept_operation()
recent_shut_off = self.recently(self.get_timeline_elements(
"vm.Instance.shut_off"))
if not self.check_operation_result(recent_shut_off):
print ("Selenium cannot shut off the "
"chosen template virtual machine")
raise Exception('Cannot shut off the virtual machine')
self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR,
"a[href$='/op/save_as_template/']"))))
self.wait_and_accept_operation(name)
return name
except:
raise Exception(
'Selenium cannot save a vm as a template')
def create_base_template(self, name=None, architecture="x86-64",
method=None, op_system=None, lease=None,
network="vm"):
if name is None:
name = "template_new_%s" % client_name
if op_system is None:
op_system = "!os %s" % client_name
try:
self.driver.get('%s/dashboard/template/choose/' % host)
self.driver.find_element_by_css_selector(
"input[type='radio'][value='base_vm']").click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
template_name = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'id_name')))
template_name.clear()
template_name.send_keys(name)
self.select_option(self.driver.find_element_by_id(
"id_arch"), architecture)
self.select_option(self.driver.find_element_by_id(
"id_access_method"), method)
system_name = self.driver.find_element_by_id("id_system")
system_name.clear()
system_name.send_keys(op_system)
self.select_option(self.driver.find_element_by_id(
"id_lease"), lease)
self.select_option(self.driver.find_element_by_id(
"id_networks"), network)
self.driver.find_element_by_css_selector(
"input.btn[type='submit']").click()
return self.save_template_from_vm(name)
except:
raise Exception(
'Selenium cannot create a base template virtual machine')
def get_template_id(self, name=None, from_all=False):
"""
In default settings find all templates ID in the template list.
If name is specified searches that specific template's ID
from_all sets whether to use owned templates or all of them
Returns list of the templates ID
"""
try:
self.driver.get('%s/dashboard/template/list/' % host)
css_selector_of_a_template = ("a[data-original-title]"
"[href*='/dashboard/template/']")
if from_all:
self.select_option(self.driver.find_element_by_id(
'id_stype'), "all")
self.driver.find_element_by_css_selector(
"button[type='submit']").click()
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.presence_of_element_located((
By.CSS_SELECTOR, css_selector_of_a_template)))
except:
print "Selenium could not locate any templates"
template_table = self.driver.find_element_by_css_selector(
"table[class*='template-list-table']")
templates = template_table.find_elements_by_css_selector("td.name")
found_template_ids = []
for template in templates:
if name is None or name in template.text:
try:
template_link = template.find_element_by_css_selector(
css_selector_of_a_template)
template_id = re.search(
r'\d+',
template_link.get_attribute("outerHTML")).group()
found_template_ids.append(template_id)
print ("Found '%(name)s' template's ID as %(id)s" % {
'name': template.text,
'id': template_id})
except NoSuchElementException:
pass
except:
raise
if not found_template_ids and name is not None:
print ("Selenium could not find the specified "
"%(name)s template in the list" % {
'name': name})
return found_template_ids
except:
raise Exception(
'Selenium cannot found the template\'s id')
def check_operation_result(self, operation_id, restore=True):
"""
Returns wheter the operation_id result is success (returns: boolean)
"""
try:
if restore:
url_base = urlparse.urlparse(self.driver.current_url)
url_save = ("%(host)s%(url)s" % {
'host': host,
'url': urlparse.urljoin(url_base.path, url_base.query)})
if url_base.fragment:
url_save = ("%(url)s#%(fragment)s" % {
'url': url_save,
'fragment': url_base.fragment})
self.driver.get('%(host)s/dashboard/vm/activity/%(id)s/' % {
'host': host,
'id': operation_id})
result = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity_status")))
print ("%(id)s result text is '%(result)s'" % {
'id': operation_id,
'result': result.text})
if (result.text == "success"):
out = True
elif (result.text == "wait"):
time.sleep(2)
out = self.check_operation_result(operation_id, False)
else:
out = False
if restore:
print "Restoring to %s url" % url_save
self.driver.get(url_save)
return out
except:
raise Exception(
'Selenium cannot check the result of an operation')
def recently(self, timeline_dict, second=90):
try:
if isinstance(timeline_dict, dict):
for key, value in timeline_dict.iteritems():
time = datetime.strptime(key, '%Y-%m-%d %H:%M')
delta = datetime.now() - time
if delta.total_seconds() <= second:
return value
except:
raise Exception(
'Selenium cannot filter timeline activities to recent')
def get_timeline_elements(self, code=None):
try:
if code is None:
css_activity_selector = "div[data-activity-code]"
code = "all activity"
else:
css_activity_selector = ("div[data-activity-code="
"'%(code)s']" % {
'code': code})
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CSS_SELECTOR, "a[href*='#activity']"))).click()
activity_dict = {}
timeline = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "activity-timeline")))
searched_activity = timeline.find_elements_by_css_selector(
css_activity_selector)
print "Found activity list for %s:" % code
for activity in searched_activity:
activity_id = activity.get_attribute('data-activity-id')
activity_text = activity.text
key = re.search(
r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1]
print ("%(id)s @ %(activity)s" % {
'id': activity_id,
'activity': key})
activity_dict[key] = activity_id
return activity_dict
except:
raise Exception('Selenium cannot find the searched activity')
def create_template_from_base(self, delete_disk=True, name=None):
try:
if name is None:
name = "template_from_base_%s" % client_name
self.driver.get('%s/dashboard/template/choose/' % host)
choice_list = []
choices = self.driver.find_elements_by_css_selector(
"input[type='radio']")
choice_list = [item for item in choices if (
'test' not in item.get_attribute('value')
and item.get_attribute('value') != 'base_vm')]
chosen = random.randint(0, len(choice_list) - 1)
choice_list[chosen].click()
self.driver.find_element_by_id(
"template-choose-next-button").click()
if delete_disk:
self.click_on_link(
self.get_link_by_href("#resources"))
disks = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-resources-disk')))
disk_list = disks.find_elements_by_css_selector(
"h4[class*='list-group-item-heading']")
if len(disk_list) > 0:
self.click_on_link(
self.get_link_by_href("/op/remove_disk/"))
self.wait_and_accept_operation()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, "_activity")))
recent_remove_disk = self.recently(
self.get_timeline_elements(
"vm.Instance.remove_disk"))
if not self.check_operation_result(recent_remove_disk):
print ("Selenium cannot delete disk "
"of the chosen template")
raise Exception('Cannot delete disk')
return self.save_template_from_vm(name)
except:
raise Exception('Selenium cannot start a template from a base one')
def delete_template(self, template_id):
try:
self.driver.get('%s/dashboard/template/%s/' % (host, template_id))
url = urlparse.urlparse(self.driver.current_url)
self.click_on_link(
self.get_link_by_href(
"/dashboard/template/delete/%s/" % template_id))
self.wait_and_accept_operation()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
if "/template/list/" not in url.path:
raise Exception()
except:
raise Exception('Selenium cannot delete the desired template')
def create_random_vm(self):
try:
self.driver.get('%s/dashboard/vm/create/' % host)
vm_list = []
pk = None
vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary')
choice = random.randint(0, len(vm_list) - 1)
vm_list[choice].click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.element_to_be_clickable((
By.CLASS_NAME, 'vm-create-start'))).click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url)
pk = re.search(r'\d+', url.path).group()
return pk
except:
raise Exception('Selenium cannot start a VM')
def view_change(self, target_box):
driver = self.driver
driver.get('%s/dashboard/' % host)
list_view = driver.find_element_by_id('%s-list-view' % target_box)
graph_view = driver.find_element_by_id('%s-graph-view' % target_box)
js_script = 'return arguments[0].style.display;'
required_attributes = {'data-index-box': target_box}
graph_view_link = self.get_link_by_href(
'#index-graph-view',
required_attributes).find_element_by_tag_name('i')
list_view_link = self.get_link_by_href(
'#index-list-view',
required_attributes).find_element_by_tag_name('i')
self.click_on_link(list_view_link)
states = [driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)]
self.click_on_link(graph_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
self.click_on_link(list_view_link)
states.extend([driver.execute_script(js_script, list_view),
driver.execute_script(js_script, graph_view)])
return states
def delete_vm(self, pk):
try:
# For relability reasons instead of using the JS operatation
self.driver.get("%(host)s/dashboard/vm/%(id)s/op/destroy/" % {
'host': host,
'id': pk})
self.wait_and_accept_operation()
try:
status_span = WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'vm-details-state')))
WebDriverWait(status_span, wait_max_sec).until(
ec.visibility_of_element_located((
By.CLASS_NAME, 'fa-trash-o')))
except:
# Selenium can time-out by not realising the JS refresh
recent_destroy_vm = self.recently(
self.get_timeline_elements("vm.Instance.destroy"))
if not self.check_operation_result(recent_destroy_vm):
print ("Selenium cannot destroy "
"the chosen %(id)s vm" % {
'id': pk})
raise Exception('Cannot destroy the specified vm')
self.driver.get('%s/dashboard/vm/%s/' % (host, pk))
try:
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.CSS_SELECTOR,
"span[data-status*='DESTROYED']")))
return True
except:
return False
except:
raise Exception("Selenium can not destroy a VM")
class VmDetailTest(UtilityMixin, SeleniumTestCase):
template_ids = []
vm_ids = []
@classmethod
def setup_class(cls):
cls._user = User.objects.create(username=client_name,
is_superuser=True)
cls._user.set_password(random_accents)
cls._user.save()
@classmethod
def teardown_class(cls):
cls._user.delete()
def test_01_login(self):
title = 'Dashboard | CIRCLE'
location = '/dashboard/'
self.login(client_name, random_accents)
self.driver.get('%s/dashboard/' % host)
url = urlparse.urlparse(self.driver.current_url)
(self.assertIn('%s' % title, self.driver.title,
'%s is not found in the title' % title) or
self.assertEqual(url.path, '%s' % location,
'URL path is not equal with %s' % location))
def test_02_add_template_rights(self):
self.login(client_name, random_accents)
template_pool = self.get_template_id(from_all=True)
if len(template_pool) > 1:
chosen = template_pool[random.randint(0, len(template_pool) - 1)]
elif len(template_pool) == 1:
chosen = template_pool[0]
else:
print "Selenium did not found any templates"
raise Exception(
"System did not meet required conditions to continue")
self.driver.get('%s/dashboard/template/%s/' % (host, chosen))
acces_form = self.driver.find_element_by_css_selector(
"form[action*='/dashboard/template/%(template_id)s/acl/']"
"[method='post']" % {
'template_id': chosen})
user_name = acces_form.find_element_by_css_selector(
"input[type='text'][id='id_name']")
user_status = acces_form.find_element_by_css_selector(
"select[name='level']")
user_name.clear()
user_name.send_keys(client_name)
self.select_option(user_status)
# For strange reasons clicking on submit button doesn't work anymore
acces_form.submit()
found_users = []
acl_users = self.driver.find_elements_by_css_selector(
"a[href*='/dashboard/profile/']")
for user in acl_users:
user_text = re.split(r':[ ]?', user.text)
if len(user_text) == 2:
found_name = re.search(r'[\w\W]+(?=\))', user_text[1]).group()
print ("'%(user)s' found in ACL list for template %(id)s" % {
'user': found_name,
'id': chosen})
found_users.append(found_name)
self.assertIn(client_name, found_users,
"Could not add user to template's ACL")
def test_03_able_to_create_template(self):
self.login(client_name, random_accents)
template_list = None
create_template = self.get_link_by_href('/dashboard/template/choose/')
self.click_on_link(create_template)
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'confirmation-modal')))
template_list = self.driver.find_elements_by_class_name(
'template-choose-list-element')
print 'Selenium found %s template possibilities' % len(template_list)
(self.assertIsNotNone(
template_list, "Selenium can not find the create template list") or
self.assertGreater(len(template_list), 0,
"The create template list is empty"))
def test_04_create_base_template(self):
self.login(client_name, random_accents)
created_template_id = self.get_template_id(
self.create_base_template())
found = created_template_id is not None
if found:
self.template_ids.extend(created_template_id)
self.assertTrue(
found,
"Could not found the created template in the template list")
def test_05_create_template_from_base(self):
self.login(client_name, random_accents)
created_template_id = self.get_template_id(
self.create_template_from_base())
found = created_template_id is not None
if found:
self.template_ids.extend(created_template_id)
self.assertTrue(
found,
"Could not found the created template in the template list")
def test_06_delete_templates(self):
success = False
self.login(client_name, random_accents)
for template_id in self.template_ids:
print "Deleting template %s" % template_id
self.delete_template(template_id)
existing_templates = self.get_template_id()
if len(existing_templates) == 0:
success = True
else:
for template_id in self.template_ids:
if template_id not in existing_templates:
self.template_ids.remove(template_id)
if len(self.template_ids) == 0:
success = True
self.assertTrue(
success, "Could not delete (all) the test template(s)")
def test_07_able_to_create_vm(self):
self.login(client_name, random_accents)
vm_list = None
create_vm_link = self.get_link_by_href('/dashboard/vm/create/')
create_vm_link.click()
WebDriverWait(self.driver, wait_max_sec).until(
ec.visibility_of_element_located((
By.ID, 'confirmation-modal')))
vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary')
print ("Selenium found %(vm_number)s virtual machine template "
" possibilities" % {
'vm_number': len(vm_list)})
(self.assertIsNotNone(
vm_list, "Selenium can not find the VM list") or
self.assertGreater(len(vm_list), 0, "The create VM list is empty"))
def test_08_create_vm(self):
self.login(client_name, random_accents)
pk = self.create_random_vm()
self.vm_ids.append(pk)
self.assertIsNotNone(pk, "Can not create a VM")
def test_09_vm_view_change(self):
self.login(client_name, random_accents)
expected_states = ["", "none",
"none", "",
"block", "none"]
states = self.view_change("vm")
print 'states: [%s]' % ', '.join(map(str, states))
print 'expected: [%s]' % ', '.join(map(str, expected_states))
self.assertListEqual(states, expected_states,
"The view mode does not change for VM listing")
def test_10_node_view_change(self):
self.login(client_name, random_accents)
expected_states = ["", "none",
"none", "",
"block", "none"]
states = self.view_change("node")
print 'states: [%s]' % ', '.join(map(str, states))
print 'expected: [%s]' % ', '.join(map(str, expected_states))
self.assertListEqual(states, expected_states,
"The view mode does not change for NODE listing")
def test_11_delete_vm(self):
self.login(client_name, random_accents)
succes = True
for vm in self.vm_ids:
if not self.delete_vm(vm):
succes = False
else:
self.vm_ids.remove(vm)
self.assertTrue(succes, "Can not delete all VM")
...@@ -24,10 +24,12 @@ from django.contrib.auth.models import User, Group ...@@ -24,10 +24,12 @@ from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from common.tests.celery_mock import MockCeleryMixin
from dashboard.views import VmAddInterfaceView from dashboard.views import VmAddInterfaceView
from vm.models import Instance, InstanceTemplate, Lease, Node, Trait from vm.models import Instance, InstanceTemplate, Lease, Node, Trait
from vm.operations import (WakeUpOperation, AddInterfaceOperation, from vm.operations import (WakeUpOperation, AddInterfaceOperation,
AddPortOperation, RemoveInterfaceOperation) AddPortOperation, RemoveInterfaceOperation,
DeployOperation)
from ..models import Profile from ..models import Profile
from firewall.models import Vlan, Host, VlanGroup from firewall.models import Vlan, Host, VlanGroup
from mock import Mock, patch from mock import Mock, patch
...@@ -44,7 +46,7 @@ class LoginMixin(object): ...@@ -44,7 +46,7 @@ class LoginMixin(object):
self.assertNotEqual(response.status_code, 403) self.assertNotEqual(response.status_code, 403)
class VmDetailTest(LoginMixin, TestCase): class VmDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -217,21 +219,25 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -217,21 +219,25 @@ class VmDetailTest(LoginMixin, TestCase):
self.login(c, 'user1') self.login(c, 'user1')
InstanceTemplate.objects.get(id=1).set_level(self.u1, 'user') InstanceTemplate.objects.get(id=1).set_level(self.u1, 'user')
Vlan.objects.get(id=1).set_level(self.u1, 'user') Vlan.objects.get(id=1).set_level(self.u1, 'user')
response = c.post('/dashboard/vm/create/', with patch.object(DeployOperation, 'async') as async:
{'template': 1, response = c.post('/dashboard/vm/create/',
'system': "bubi", {'template': 1,
'cpu_priority': 1, 'cpu_count': 1, 'system': "bubi",
'ram_size': 1000}) 'cpu_priority': 1, 'cpu_count': 1,
'ram_size': 1000})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_use_permitted_template_superuser(self): def test_use_permitted_template_superuser(self):
c = Client() c = Client()
self.login(c, 'superuser') self.login(c, 'superuser')
response = c.post('/dashboard/vm/create/', with patch.object(DeployOperation, 'async') as async:
{'template': 1, response = c.post('/dashboard/vm/create/',
'system': "bubi", {'template': 1,
'cpu_priority': 1, 'cpu_count': 1, 'system': "bubi",
'ram_size': 1000}) 'cpu_priority': 1, 'cpu_count': 1,
'ram_size': 1000})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_edit_unpermitted_template(self): def test_edit_unpermitted_template(self):
...@@ -537,15 +543,17 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -537,15 +543,17 @@ class VmDetailTest(LoginMixin, TestCase):
self.login(c, "superuser") self.login(c, "superuser")
instance_count = Instance.objects.all().count() instance_count = Instance.objects.all().count()
response = c.post("/dashboard/vm/create/", { with patch.object(DeployOperation, 'async') as async:
'name': 'vm', response = c.post("/dashboard/vm/create/", {
'amount': 2, 'name': 'vm',
'customized': 1, 'amount': 2,
'template': 1, 'customized': 1,
'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128, 'template': 1,
'network': [], 'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128,
}) 'network': [],
})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(instance_count + 2, Instance.objects.all().count()) self.assertEqual(instance_count + 2, Instance.objects.all().count())
...@@ -585,7 +593,7 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -585,7 +593,7 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo") self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
class NodeDetailTest(LoginMixin, TestCase): class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -756,7 +764,7 @@ class NodeDetailTest(LoginMixin, TestCase): ...@@ -756,7 +764,7 @@ class NodeDetailTest(LoginMixin, TestCase):
self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count) self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count)
class GroupCreateTest(LoginMixin, TestCase): class GroupCreateTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -858,7 +866,7 @@ class GroupCreateTest(LoginMixin, TestCase): ...@@ -858,7 +866,7 @@ class GroupCreateTest(LoginMixin, TestCase):
self.assertTrue(newgroup.profile.has_level(self.u0, 'owner')) self.assertTrue(newgroup.profile.has_level(self.u0, 'owner'))
class GroupDeleteTest(LoginMixin, TestCase): class GroupDeleteTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -948,7 +956,7 @@ class GroupDeleteTest(LoginMixin, TestCase): ...@@ -948,7 +956,7 @@ class GroupDeleteTest(LoginMixin, TestCase):
self.assertEqual(Group.objects.count(), groupnum - 1) self.assertEqual(Group.objects.count(), groupnum - 1)
class GroupDetailTest(LoginMixin, TestCase): class GroupDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1332,7 +1340,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1332,7 +1340,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
class GroupListTest(LoginMixin, TestCase): class GroupListTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1375,7 +1383,7 @@ class GroupListTest(LoginMixin, TestCase): ...@@ -1375,7 +1383,7 @@ class GroupListTest(LoginMixin, TestCase):
self.g2.delete() self.g2.delete()
class VmDetailVncTest(LoginMixin, TestCase): class VmDetailVncTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1408,7 +1416,7 @@ class VmDetailVncTest(LoginMixin, TestCase): ...@@ -1408,7 +1416,7 @@ class VmDetailVncTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
class TransferOwnershipViewTest(LoginMixin, TestCase): class TransferOwnershipViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json'] fixtures = ['test-vm-fixture.json']
def setUp(self): def setUp(self):
...@@ -1446,7 +1454,7 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1446,7 +1454,7 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
self.assertEqual(self.u2.notification_set.count(), c2 + 1) self.assertEqual(self.u2.notification_set.count(), c2 + 1)
class IndexViewTest(LoginMixin, TestCase): class IndexViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1548,7 +1556,7 @@ class ProfileViewTest(LoginMixin, TestCase): ...@@ -1548,7 +1556,7 @@ class ProfileViewTest(LoginMixin, TestCase):
self.assertIsNone(authenticate(username="user1", password="asd")) self.assertIsNone(authenticate(username="user1", password="asd"))
class AclViewTest(LoginMixin, TestCase): class AclViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1666,7 +1674,7 @@ class AclViewTest(LoginMixin, TestCase): ...@@ -1666,7 +1674,7 @@ class AclViewTest(LoginMixin, TestCase):
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
class VmListTest(LoginMixin, TestCase): class VmListTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
......
...@@ -102,7 +102,8 @@ class NodeDetailView(LoginRequiredMixin, ...@@ -102,7 +102,8 @@ class NodeDetailView(LoginRequiredMixin,
).order_by('-started').select_related() ).order_by('-started').select_related()
context['ops'] = get_operations(self.object, self.request.user) context['ops'] = get_operations(self.object, self.request.user)
context['op'] = {i.op: i for i in context['ops']} context['op'] = {i.op: i for i in context['ops']}
context['activities'] = na context['show_show_all'] = len(na) > 10
context['activities'] = na[:10]
context['trait_form'] = form context['trait_form'] = form
context['graphite_enabled'] = ( context['graphite_enabled'] = (
settings.GRAPHITE_URL is not None) settings.GRAPHITE_URL is not None)
...@@ -294,15 +295,21 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView): ...@@ -294,15 +295,21 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View): class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
def get(self, request, pk): def get(self, request, pk):
show_all = request.GET.get("show_all", "false") == "true"
node = Node.objects.get(pk=pk) node = Node.objects.get(pk=pk)
activities = NodeActivity.objects.filter( activities = NodeActivity.objects.filter(
node=node, parent=None).order_by('-started').select_related() node=node, parent=None).order_by('-started').select_related()
show_show_all = len(activities) > 10
if not show_all:
activities = activities[:10]
response = { response = {
'activities': render_to_string( 'activities': render_to_string(
"dashboard/node-detail/_activity-timeline.html", "dashboard/node-detail/_activity-timeline.html",
RequestContext(request, {'activities': activities})) RequestContext(request, {'activities': activities,
'show_show_all': show_show_all}))
} }
return HttpResponse( return HttpResponse(
......
...@@ -135,6 +135,18 @@ def test(test=""): ...@@ -135,6 +135,18 @@ def test(test=""):
run("./manage.py test --settings=circle.settings.test %s" % test) run("./manage.py test --settings=circle.settings.test %s" % test)
@roles('portal')
def selenium(test=""):
"Run selenium tests"
with _workon("circle"), cd("~/circle/circle"):
if test == "f":
test = "--failed"
else:
test += " --with-id"
run("xvfb-run ./manage.py test "
"--settings=circle.settings.selenium_test %s" % test)
def pull(dir="~/circle/circle"): def pull(dir="~/circle/circle"):
"Pull from upstream branch (stash any changes)" "Pull from upstream branch (stash any changes)"
now = unicode(datetime.datetime.now()) now = unicode(datetime.datetime.now())
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('firewall', '0003_auto_20150226_1927'),
]
operations = [
migrations.AlterModelOptions(
name='blacklistitem',
options={'ordering': ('id',), 'verbose_name': 'blacklist item', 'verbose_name_plural': 'blacklist items'},
),
migrations.AlterModelOptions(
name='domain',
options={'ordering': ('id',), 'verbose_name': 'domain', 'verbose_name_plural': 'domains'},
),
migrations.AlterModelOptions(
name='ethernetdevice',
options={'ordering': ('id',), 'verbose_name': 'ethernet device', 'verbose_name_plural': 'ethernet devices'},
),
migrations.AlterModelOptions(
name='firewall',
options={'ordering': ('id',), 'verbose_name': 'firewall', 'verbose_name_plural': 'firewalls'},
),
migrations.AlterModelOptions(
name='group',
options={'ordering': ('id',), 'verbose_name': 'host group', 'verbose_name_plural': 'host groups'},
),
migrations.AlterModelOptions(
name='record',
options={'ordering': ('domain', 'name'), 'verbose_name': 'record', 'verbose_name_plural': 'records'},
),
migrations.AlterModelOptions(
name='switchport',
options={'ordering': ('id',), 'verbose_name': 'switch port', 'verbose_name_plural': 'switch ports'},
),
migrations.AlterModelOptions(
name='vlan',
options={'ordering': ('vid',), 'verbose_name': 'vlan', 'verbose_name_plural': 'vlans'},
),
migrations.AlterModelOptions(
name='vlangroup',
options={'ordering': ('id',), 'verbose_name': 'vlan group', 'verbose_name_plural': 'vlan groups'},
),
]
...@@ -391,6 +391,11 @@ class Vlan(AclBase, models.Model): ...@@ -391,6 +391,11 @@ class Vlan(AclBase, models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at')) verbose_name=_('modified at'))
class Meta:
verbose_name = _("vlan")
verbose_name_plural = _("vlans")
ordering = ('vid', )
def clean(self): def clean(self):
super(Vlan, self).clean() super(Vlan, self).clean()
if self.ipv6_template: if self.ipv6_template:
...@@ -540,6 +545,11 @@ class VlanGroup(models.Model): ...@@ -540,6 +545,11 @@ class VlanGroup(models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at')) verbose_name=_('modified at'))
class Meta:
verbose_name = _("vlan group")
verbose_name_plural = _("vlan groups")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -562,6 +572,11 @@ class Group(models.Model): ...@@ -562,6 +572,11 @@ class Group(models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified at')) verbose_name=_('modified at'))
class Meta:
verbose_name = _("host group")
verbose_name_plural = _("host groups")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -935,6 +950,11 @@ class Firewall(models.Model): ...@@ -935,6 +950,11 @@ class Firewall(models.Model):
name = models.CharField(max_length=20, unique=True, name = models.CharField(max_length=20, unique=True,
verbose_name=_('name')) verbose_name=_('name'))
class Meta:
verbose_name = _("firewall")
verbose_name_plural = _("firewalls")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -981,6 +1001,11 @@ class Domain(models.Model): ...@@ -981,6 +1001,11 @@ class Domain(models.Model):
ttl = models.IntegerField(default=600, verbose_name=_('ttl')) ttl = models.IntegerField(default=600, verbose_name=_('ttl'))
description = models.TextField(blank=True, verbose_name=_('description')) description = models.TextField(blank=True, verbose_name=_('description'))
class Meta:
verbose_name = _("domain")
verbose_name_plural = _("domains")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -1058,6 +1083,8 @@ class Record(models.Model): ...@@ -1058,6 +1083,8 @@ class Record(models.Model):
return reverse('network.record', kwargs={'pk': self.pk}) return reverse('network.record', kwargs={'pk': self.pk})
class Meta: class Meta:
verbose_name = _("record")
verbose_name_plural = _("records")
ordering = ( ordering = (
'domain', 'domain',
'name', 'name',
...@@ -1077,6 +1104,11 @@ class SwitchPort(models.Model): ...@@ -1077,6 +1104,11 @@ class SwitchPort(models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified_at')) verbose_name=_('modified_at'))
class Meta:
verbose_name = _("switch port")
verbose_name_plural = _("switch ports")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
devices = ','.join(self.ethernet_devices.values_list('name', devices = ','.join(self.ethernet_devices.values_list('name',
flat=True)) flat=True))
...@@ -1104,6 +1136,11 @@ class EthernetDevice(models.Model): ...@@ -1104,6 +1136,11 @@ class EthernetDevice(models.Model):
modified_at = models.DateTimeField(auto_now=True, modified_at = models.DateTimeField(auto_now=True,
verbose_name=_('modified_at')) verbose_name=_('modified_at'))
class Meta:
verbose_name = _("ethernet device")
verbose_name_plural = _("ethernet devices")
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.name return self.name
...@@ -1136,7 +1173,8 @@ class BlacklistItem(models.Model): ...@@ -1136,7 +1173,8 @@ class BlacklistItem(models.Model):
class Meta(object): class Meta(object):
verbose_name = _('blacklist item') verbose_name = _('blacklist item')
verbose_name_plural = _('blacklist') verbose_name_plural = _('blacklist items')
ordering = ('id', )
def get_absolute_url(self): def get_absolute_url(self):
return reverse('network.blacklist', kwargs={'pk': self.pk}) return reverse('network.blacklist', kwargs={'pk': self.pk})
......
...@@ -15,20 +15,22 @@ ...@@ -15,20 +15,22 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from mock import patch
from netaddr import IPSet, AddrFormatError from netaddr import IPSet, AddrFormatError
from django.test import TestCase import django.conf
from django.contrib.auth.models import User from django.contrib.auth.models import User
from ..admin import HostAdmin from django.forms import ValidationError
from django.test import TestCase
from common.tests.celery_mock import MockCeleryMixin
from firewall.admin import HostAdmin
from firewall.fw import dns, ipv6_to_octal
from firewall.iptables import IptRule, IptChain, InvalidRuleExcepion
from firewall.models import (Vlan, Domain, Record, Host, VlanGroup, Group, from firewall.models import (Vlan, Domain, Record, Host, VlanGroup, Group,
Rule, Firewall) Rule, Firewall)
from firewall.fw import dns, ipv6_to_octal
from firewall.tasks.local_tasks import reloadtask_worker, reloadtask from firewall.tasks.local_tasks import reloadtask_worker, reloadtask
from django.forms import ValidationError
from ..iptables import IptRule, IptChain, InvalidRuleExcepion
from mock import patch
import django.conf
settings = django.conf.settings.FIREWALL_SETTINGS settings = django.conf.settings.FIREWALL_SETTINGS
...@@ -68,7 +70,7 @@ class HostAdminTestCase(TestCase): ...@@ -68,7 +70,7 @@ class HostAdminTestCase(TestCase):
self.assertEqual(l, "alma, korte, szilva") self.assertEqual(l, "alma, korte, szilva")
class GetNewAddressTestCase(TestCase): class GetNewAddressTestCase(MockCeleryMixin, TestCase):
def setUp(self): def setUp(self):
self.u1 = User.objects.create(username='user1') self.u1 = User.objects.create(username='user1')
self.u1.save() self.u1.save()
...@@ -105,7 +107,7 @@ class GetNewAddressTestCase(TestCase): ...@@ -105,7 +107,7 @@ class GetNewAddressTestCase(TestCase):
assert self.vlan.get_new_address()['ipv4'] not in used_v4 assert self.vlan.get_new_address()['ipv4'] not in used_v4
class HostGetHostnameTestCase(TestCase): class HostGetHostnameTestCase(MockCeleryMixin, TestCase):
def setUp(self): def setUp(self):
self.u1 = User.objects.create(username='user1') self.u1 = User.objects.create(username='user1')
self.u1.save() self.u1.save()
...@@ -187,7 +189,7 @@ class IptablesTestCase(TestCase): ...@@ -187,7 +189,7 @@ class IptablesTestCase(TestCase):
self.assertEqual(len(compiled_v6.splitlines()), 0) self.assertEqual(len(compiled_v6.splitlines()), 0)
class ReloadTestCase(TestCase): class ReloadTestCase(MockCeleryMixin, TestCase):
def setUp(self): def setUp(self):
self.u1 = User.objects.create(username='user1') self.u1 = User.objects.create(username='user1')
self.u1.save() self.u1.save()
...@@ -313,7 +315,22 @@ class ReloadTestCase(TestCase): ...@@ -313,7 +315,22 @@ class ReloadTestCase(TestCase):
def test_periodic_task(self): def test_periodic_task(self):
# TODO # TODO
with patch('firewall.tasks.local_tasks.cache') as cache: cache = patch('firewall.tasks.local_tasks.cache')
grqn = patch('firewall.models.Firewall.get_remote_queue_name',
return_value='fw.firewall')
worker = patch(
'firewall.tasks.local_tasks.reloadtask_worker.apply_async')
dns = patch('firewall.tasks.remote_tasks.reload_dns.apply_async')
fw = patch(
'firewall.tasks.remote_tasks.reload_firewall.apply_async')
fw_vlan = patch(
'firewall.tasks.remote_tasks.reload_firewall_vlan.apply_async')
blacklist = patch(
'firewall.tasks.remote_tasks.reload_blacklist.apply_async')
dhcp = patch('firewall.tasks.remote_tasks.reload_dhcp.apply_async')
with cache as cache, grqn, dns, fw, fw_vlan, blacklist, dhcp, worker:
self.test_host_add_port() self.test_host_add_port()
self.test_host_add_port2() self.test_host_add_port2()
reloadtask_worker() reloadtask_worker()
......
...@@ -20,6 +20,7 @@ from django.test.client import Client ...@@ -20,6 +20,7 @@ from django.test.client import Client
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from mock import Mock from mock import Mock
from common.tests.celery_mock import MockCeleryMixin
from dashboard.tests.test_views import LoginMixin from dashboard.tests.test_views import LoginMixin
from vm.models import Instance from vm.models import Instance
...@@ -29,7 +30,7 @@ import django.conf ...@@ -29,7 +30,7 @@ import django.conf
settings = django.conf.settings.FIREWALL_SETTINGS settings = django.conf.settings.FIREWALL_SETTINGS
class VlanAclTest(LoginMixin, TestCase): class VlanAclTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
......
...@@ -56,7 +56,7 @@ pre_state_changed = Signal(providing_args=["new_state"]) ...@@ -56,7 +56,7 @@ pre_state_changed = Signal(providing_args=["new_state"])
post_state_changed = Signal(providing_args=["new_state"]) post_state_changed = Signal(providing_args=["new_state"])
pwgen = partial(User.objects.make_random_password, pwgen = partial(User.objects.make_random_password,
allowed_chars='abcdefghijklmnopqrstuvwx' allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX0123456789') 'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
scheduler = import_module(name=django.conf.settings.VM_SCHEDULER) scheduler = import_module(name=django.conf.settings.VM_SCHEDULER)
ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS ACCESS_PROTOCOLS = django.conf.settings.VM_ACCESS_PROTOCOLS
...@@ -818,7 +818,9 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -818,7 +818,9 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
return acts return acts
def get_merged_activities(self, user=None): def get_merged_activities(self, user=None):
whitelist = ("create_disk", "download_disk", "add_port", "remove_port") whitelist = ("create_disk", "download_disk", "remove_disk",
"add_port", "remove_port",
"add_interface", "remove_interface")
acts = self.get_activities(user) acts = self.get_activities(user)
merged_acts = [] merged_acts = []
latest = None latest = None
......
...@@ -377,7 +377,12 @@ class DeployOperation(InstanceOperation): ...@@ -377,7 +377,12 @@ class DeployOperation(InstanceOperation):
self.instance.allocate_node() self.instance.allocate_node()
# Deploy virtual images # Deploy virtual images
self.instance._deploy_disks(parent_activity=activity) try:
self.instance._deploy_disks(parent_activity=activity)
except:
self.instance.yield_node()
self.instance.yield_vnc_port()
raise
# Deploy VM on remote machine # Deploy VM on remote machine
if self.instance.state not in ['PAUSED']: if self.instance.state not in ['PAUSED']:
...@@ -452,6 +457,9 @@ class DestroyOperation(InstanceOperation): ...@@ -452,6 +457,9 @@ class DestroyOperation(InstanceOperation):
required_perms = () required_perms = ()
resultant_state = 'DESTROYED' resultant_state = 'DESTROYED'
def on_abort(self, activity, error):
activity.resultant_state = None
def _operation(self, activity, system): def _operation(self, activity, system):
# Destroy networks # Destroy networks
with activity.sub_activity( with activity.sub_activity(
...@@ -1093,16 +1101,17 @@ class ResetNodeOperation(NodeOperation): ...@@ -1093,16 +1101,17 @@ class ResetNodeOperation(NodeOperation):
"You cannot reset a disabled or online node."), Exception()) "You cannot reset a disabled or online node."), Exception())
def _operation(self, activity, user): def _operation(self, activity, user):
if self.node.enabled:
DisableOperation(self.node).call(parent_activity=activity,
user=user)
for i in self.node.instance_set.all(): for i in self.node.instance_set.all():
name = create_readable(ugettext_noop( name = create_readable(ugettext_noop(
"migrate %(instance)s (%(pk)s)"), instance=i.name, pk=i.pk) "redeploy %(instance)s (%(pk)s)"), instance=i.name, pk=i.pk)
with activity.sub_activity('migrate_instance_%d' % i.pk, with activity.sub_activity('migrate_instance_%d' % i.pk,
readable_name=name): readable_name=name):
i.redeploy(user=user) i.redeploy(user=user)
self.node.enabled = False
self.node.schedule_enabled = False
self.node.save()
@register_operation @register_operation
class FlushOperation(NodeOperation): class FlushOperation(NodeOperation):
......
...@@ -24,6 +24,8 @@ from django.contrib.auth.models import User ...@@ -24,6 +24,8 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.tests.celery_mock import MockCeleryMixin
from ..models import ( from ..models import (
Lease, Node, Interface, Instance, InstanceTemplate, InstanceActivity, Lease, Node, Interface, Instance, InstanceTemplate, InstanceActivity,
) )
...@@ -166,7 +168,7 @@ class InstanceTestCase(TestCase): ...@@ -166,7 +168,7 @@ class InstanceTestCase(TestCase):
self.assertEqual(Instance.get_status_icon(inst), 'fa-play') self.assertEqual(Instance.get_status_icon(inst), 'fa-play')
class InterfaceTestCase(TestCase): class InterfaceTestCase(MockCeleryMixin, TestCase):
def test_interface_create(self): def test_interface_create(self):
from firewall.models import Vlan, Domain from firewall.models import Vlan, Domain
......
...@@ -3,5 +3,9 @@ ...@@ -3,5 +3,9 @@
coverage==3.7.1 coverage==3.7.1
factory-boy==2.4.1 factory-boy==2.4.1
mock==1.0.1 mock==1.0.1
django-nose==1.2 django-nose==1.3
nose==1.3.4 nose==1.3.4
nose-exclude==0.2.0
selenium==2.45.0
selenose==1.3
-e git+https://github.com/kmmbvnr/django-jenkins.git@019774dc2f668bc66b66f90f97eb8e14ae9566a4#egg=django_jenkins-dev
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment