Merge branch 'master' of github.com:open-cloud/xos
diff --git a/.gitignore b/.gitignore
index 3fe092d..61b07cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
*.swp
profile
*.moved-aside
+.idea
+xos.iml
xos/configurations/frontend/Dockerfile
xos/core/xoslib/karma-*
xos/core/xoslib/docs
diff --git a/xos/core/__init__.py b/xos/core/__init__.py
index e69de29..e01f9cd 100644
--- a/xos/core/__init__.py
+++ b/xos/core/__init__.py
@@ -0,0 +1 @@
+# from tests import *
diff --git a/xos/core/fixtures/initial_data.json b/xos/core/fixtures/core_initial_data.json
similarity index 100%
rename from xos/core/fixtures/initial_data.json
rename to xos/core/fixtures/core_initial_data.json
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index c380e9c..a022cae 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -2,7 +2,7 @@
from .project import Project
from .singletonmodel import SingletonModel
from .service import Service, Tenant, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, Subscriber, Provider
-from .service import ServiceAttribute, TenantAttribute
+from .service import ServiceAttribute, TenantAttribute, ServiceRole
from .tag import Tag
from .role import Role
from .site import Site, Deployment, DeploymentRole, DeploymentPrivilege, Controller, ControllerRole, ControllerSite, SiteDeployment
diff --git a/xos/core/models/slice.py b/xos/core/models/slice.py
index 84622cf..42e3a25 100644
--- a/xos/core/models/slice.py
+++ b/xos/core/models/slice.py
@@ -15,6 +15,7 @@
from core.models import Flavor, Image
from core.models.plcorebase import StrippedCharField
from django.core.exceptions import PermissionDenied, ValidationError
+from xos.exceptions import *
# Create your models here.
@@ -52,13 +53,13 @@
site = Site.objects.get(id=self.site.id)
# allow preexisting slices to keep their original name for now
if not self.id and not self.name.startswith(site.login_base):
- raise ValidationError('slice name must begin with %s' % site.login_base)
+ raise XOSValidationError('slice name must begin with %s' % site.login_base)
if self.name == site.login_base+"_":
- raise ValidationError('slice name is too short')
+ raise XOSValidationError('slice name is too short')
if " " in self.name:
- raise ValidationError('slice name must not contain spaces')
+ raise XOSValidationError('slice name must not contain spaces')
if self.serviceClass is None:
# We allowed None=True for serviceClass because Django evolution
@@ -82,7 +83,7 @@
raise PermissionDenied("Insufficient privileges to change slice creator")
if not self.creator:
- raise ValidationError('slice has no creator')
+ raise XOSValidationError('slice has no creator')
if self.network=="Private Only":
# "Private Only" was the default from the old Tenant View
diff --git a/xos/core/tests.py b/xos/core/tests.py
index e3d7faa..06bf678 100644
--- a/xos/core/tests.py
+++ b/xos/core/tests.py
@@ -1,128 +1,337 @@
+# TEST
+# To execute these tests use `python manage.py test core`
+
#!/usr/bin/env python
from django.test import TestCase
-from core.models import *
-from rest_framework.test import *
-from genapi import *
+from django.contrib.auth.models import User
+from rest_framework import status
+from rest_framework.test import APIClient
+from rest_framework.test import APITestCase
import json
-from datetime import datetime
+from django.forms.models import model_to_dict
-FIXTURES_FILE = 'core/fixtures/initial_data.json'
-MODELS = ['Deployment','Image','Node','Reservation','Slice','Instance','User']
+from core.models import *
-def is_dynamic_type(x):
- t = type(x)
- return t in [datetime]
+print "-------------------------- Let's test!!!!!!!! --------------------------"
-class APITestCase(TestCase):
- def setUp(self):
- self.init_data=json.loads(open(FIXTURES_FILE).read())
- self.data_dict={}
- self.hidden_keys={}
+from django.apps import apps
+from django.test.client import Client
+from django.test import testcases
+from django.http import SimpleCookie, HttpRequest, QueryDict
+from importlib import import_module
+from django.conf import settings
+class FixedClient(Client):
+ def login(self, **credentials):
+ """
+ Sets the Factory to appear as if it has successfully logged into a site.
- for d in self.init_data:
- model_tag = d['model']
- model_name = model_tag.split('.')[1]
+ Returns True if login is possible; False if the provided credentials
+ are incorrect, or the user is inactive, or if the sessions framework is
+ not available.
+ """
+ from django.contrib.auth import authenticate, login
+ user = authenticate(**credentials)
+ if (user and user.is_active and
+ apps.is_installed('django.contrib.sessions')):
+ engine = import_module(settings.SESSION_ENGINE)
- try:
- self.data_dict[model_name].append(d)
- except:
- self.data_dict[model_name]=[d]
+ # Create a fake request to store login details.
+ request = HttpRequest()
- # Any admin user would do
- self.calling_user = User('sapan@onlab.us')
- self.client = APIClient()
- self.client.force_authenticate(user=self.calling_user)
+ # XOS's admin.py requires these to be filled in
+ request.POST = {"username": credentials["username"],
+ "password": credentials["password"]}
+
+ if self.session:
+ request.session = self.session
+ else:
+ request.session = engine.SessionStore()
+ login(request, user)
+
+ # Save the session values.
+ request.session.save()
+
+ # Set the cookie to represent the session.
+ session_cookie = settings.SESSION_COOKIE_NAME
+ self.cookies[session_cookie] = request.session.session_key
+ cookie_data = {
+ 'max-age': None,
+ 'path': '/',
+ 'domain': settings.SESSION_COOKIE_DOMAIN,
+ 'secure': settings.SESSION_COOKIE_SECURE or None,
+ 'expires': None,
+ }
+ self.cookies[session_cookie].update(cookie_data)
+
+ return True
+ else:
+ return False
+
+class FixedAPITestCase(testcases.TestCase):
+ client_class = FixedClient
+
+# Environment Tests - Should pass everytime, if not something in the config is broken.
+class SimpleTest(TestCase):
+ fixtures = []
+
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
- def check_items(self, response, data_list):
- rdict = {}
- for r in response:
- rdict['%d'%r['id']]=r
+# Site Test
+class SiteTest(TestCase):
+ fixtures = []
- for d in data_list:
- match = True
- try:
- item = rdict['%d'%d['pk']]
- except Exception,e:
- print 'API missing item %d / %r'%(d['pk'],rdict.keys())
- raise e
+ def setUp(self):
+ Site.objects.create(
+ name="Test Site",
+ login_base="test_"
+ )
- fields=d['fields']
- fields['id']=d['pk']
-
- for k in item.keys():
- try:
- resp_val = fields[k]
- except KeyError:
- if (not self.hidden_keys.has_key(k)):
- print 'Hidden key %s'%k
- self.hidden_keys[k]=True
-
- continue
-
- if (item[k]!=resp_val and not is_dynamic_type(item[k])):
- if (type(resp_val)==type(item[k])):
- print 'Key %s did not match: 1. %r 2. %r'%(k,item[k],resp_val)
- print fields
- match = False
+ def test_read_site(self):
+ """
+ Should read a site in the DB.
+ """
+ site = Site.objects.get(name="Test Site")
+ # print(site._meta.get_all_field_names())
+ self.assertEqual(site.login_base, "test_")
+class UnautheticatedRequest(FixedAPITestCase):
+ fixtures = []
- def create(self, model, mplural, record):
- request = self.client.put('/xos/%s/'%mplural,record['fields'])
+ def test_require_authentication(self):
+ """
+ Ensure that request must be authenticated
+ """
+ response = self.client.get('/xos/sites/', format='json')
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
- #if (len2==len1):
- # raise Exception('Could not delete %s/%d'%(model,pk))
- return
+class SiteTestAPI(FixedAPITestCase):
+ fixtures = []
- def update(self, model, mplural, pk):
- src_record = self.data_dict[model.lower()][0]
- record_to_update = src_record['fields']
- now = datetime.now()
- record_to_update['enacted']=now
- response = self.client.put('/xos/%s/%d/'%(mplural,pk),record_to_update)
- self.assertEqual(response.data['enacted'],now)
+ def setUp(self):
+ self.site = Site.objects.create(
+ name="Test Site",
+ login_base="test_"
+ )
+ self.user = User(
+ username='testuser',
+ email='test@mail.org',
+ password='testing',
+ site=self.site,
+ is_admin=True
+ )
+ self.user.save()
+ self.client.login(username='test@mail.org', password='testing')
- return
+ def test_read_site_API(self):
+ """
+ Read a Site trough API
+ """
+ response = self.client.get('/xos/sites/', format='json')
+ parsed = json.loads(response.content)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(parsed), 1)
+ self.assertEqual(parsed[0]['login_base'], 'test_')
- def delete(self, model, mplural, pk):
- mclass = globals()[model]
- len1 = len(mclass.objects.all())
- response = self.client.delete('/xos/%s/%d/'%(mplural,pk))
- len2 = len(mclass.objects.all())
- self.assertNotEqual(len1,len2)
+ def test_create_site_API(self):
+ """
+ Create a Site trough API
+ """
+ data = {
+ 'name': "Another Test Site",
+ 'login_base': "another_test_",
+ 'location': [10, 20],
+ 'abbreviated_name': 'test'
+ }
+ response = self.client.post('/xos/sites/', data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(Site.objects.count(), 2)
+ self.assertEqual(Site.objects.filter(name="Another Test Site").count(), 1)
- return
- def retrieve(self, m, mplural, mlower):
- response = self.client.get('/xos/%s/'%mplural)
- #force_authenticate(request,user=self.calling_user)
- self.check_items(response.data,self.data_dict[mlower])
+class SliceTestAPI(FixedAPITestCase):
+ fixtures = []
- return
+ def setUp(self):
+ self.site = Site.objects.create(
+ name="Test Site",
+ login_base="test_"
+ )
+ self.pi = SiteRole.objects.create(role='pi')
+ self.user = User(
+ username='testuser',
+ email='test@mail.org',
+ password='testing',
+ site=self.site
+ )
+ self.user.save()
+ self.siteprivileges = SitePrivilege.objects.create(
+ user=self.user,
+ site=self.site,
+ role=self.pi
+ )
+ self.serviceClass = ServiceClass.objects.create(
+ name='Test Service Class'
+ )
+ self.client.login(username='test@mail.org', password='testing')
- def test_initial_retrieve(self):
- for m in MODELS:
- print 'Checking retrieve on %s...'%m
- self.retrieve(m, m.lower()+'s',m.lower())
+ def test_create_site_slice(self):
+ """
+ Add a slice to a given site
+ """
+ data = {
+ 'name': "test_slice",
+ 'site': self.site.id,
+ 'serviceClass': self.serviceClass.id
+ }
+ response = self.client.post('/xos/slices/?no_hyperlinks=1', data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- def test_update(self):
- for m in MODELS:
- print 'Checking update on %s...'%m
- first = self.data_dict[m.lower()][0]['pk']
- self.update(m, m.lower()+'s',int(first))
-
- def test_delete(self):
- for m in MODELS:
- print 'Checking delete on %s...'%m
- first = self.data_dict[m.lower()][0]['pk']
- self.delete(m, m.lower()+'s',int(first))
+ def test_validation_slice_name(self):
+ """
+ The slice name should start with site.login_base
+ curl -H "Accept: application/json; indent=4" -u padmin@vicci.org:letmein 'http://xos:9999/xos/slices/?no_hyperlinks=1' -H "Content-Type: application/json" -X POST --data '{"name": "test", "site":"1", "serviceClass":1}'
+ """
+ data = {
+ 'name': "wrong_slice",
+ 'site': self.site.id,
+ 'serviceClass': self.serviceClass.id
+ }
+ response = self.client.post('/xos/slices/?no_hyperlinks=1', data, format='json')
+ parsed = json.loads(response.content)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ self.assertEqual(parsed['detail']['specific_error'], "slice name must begin with test_")
- def test_create(self):
- for m in MODELS:
- print 'Checking create on %s...'%m
- first = self.data_dict[m.lower()][0]
- self.create(m, m.lower()+'s',first)
+ def test_only_admin_can_change_creator(self):
+ """
+ Only an admin can change the creator of a slice
+ """
+ slice = Slice.objects.create(
+ name="test_slice",
+ site=self.site,
+ serviceClass=self.serviceClass,
+ creator=self.user
+ )
+ user2 = User(
+ username='another_testuser',
+ email='another_test@mail.org',
+ password='testing',
+ site=self.site
+ )
+ user2.save()
+
+ data = model_to_dict(slice)
+ data['creator'] = user2.id
+ json_data = json.dumps(data)
+
+ response = self.client.put('/xos/slices/%s/?no_hyperlinks=1' % slice.id, json_data, format='json', content_type="application/json")
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ parsed = json.loads(response.content)
+ self.assertEqual(parsed['detail']['specific_error'], "Insufficient privileges to change slice creator")
+
+class ServiceTestAPI(FixedAPITestCase):
+ fixtures = []
+
+ def setUp(self):
+ self.site = Site.objects.create(
+ name="Test Site",
+ login_base="test_"
+ )
+ self.admin = User(
+ username='testadmin',
+ email='admin@mail.org',
+ password='testing',
+ site=self.site,
+ is_admin=True
+ )
+ self.admin.save()
+
+ self.user = User(
+ username='testuser',
+ email='user@mail.org',
+ password='testing',
+ site=self.site
+ )
+ self.user.save()
+
+ self.service1 = Service.objects.create(
+ name="fakeService1",
+ versionNumber="1.0.0",
+ published=True,
+ enabled=True
+ )
+
+ self.service2 = Service.objects.create(
+ name="fakeService1",
+ versionNumber="1.0.0",
+ published=True,
+ enabled=True
+ )
+
+ self.service_role = ServiceRole.objects.create(role='admin')
+
+ self.service_privileges = ServicePrivilege.objects.create(
+ user=self.user,
+ service=self.service2,
+ role=self.service_role
+ )
+
+ # TODO
+ # [x] admin view all service
+ # [ ] user view only service2
+ # [x] admin view a specific service service
+ # [ ] user view can't view a specific service
+ # [ ] user update service2
+ # [ ] usercan NOT update service2
+ # [x] admin update service1
+ def test_admin_read_all_service(self):
+ """
+ Admin should read all the services
+ """
+ self.client.login(username='admin@mail.org', password='testing')
+ response = self.client.get('/xos/services/', format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(json.loads(response.content)), 2)
+
+ # need to understand how slices are related
+ def xtest_user_read_all_service(self):
+ """
+ User should read only service for which have privileges
+ """
+ self.client.login(username='user@mail.org', password='testing')
+ response = self.client.get('/xos/services/', format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(json.loads(response.content)), 1)
+
+ def test_admin_read_one_service(self):
+ """
+ Read a given service
+ """
+ self.client.login(username='admin@mail.org', password='testing')
+ response = self.client.get('/xos/services/%s/' % self.service1.id, format='json')
+ parsed = json.loads(response.content)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(parsed['name'], self.service1.name)
+
+ def test_admin_update_service(self):
+ """
+ Update a given service
+ """
+ data = model_to_dict(self.service1)
+ data['name'] = "newName"
+ json_data = json.dumps(data)
+
+ self.client.login(username='admin@mail.org', password='testing')
+ response = self.client.put('/xos/services/%s/' % self.service1.id, json_data, format='json', content_type="application/json")
+ parsed = json.loads(response.content)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ model = Service.objects.get(id=self.service1.id)
+ self.assertEqual(model.name, data['name'])
+
\ No newline at end of file
diff --git a/xos/manage.py b/xos/manage.py
index ae4f598..219d0e7 100644
--- a/xos/manage.py
+++ b/xos/manage.py
@@ -3,6 +3,7 @@
import sys
if __name__ == "__main__":
+ os.chdir('..') # <<<---This is what you want to add
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
from django.core.management import execute_from_command_line
diff --git a/xos/services/cord/templates/vbngadmin.html b/xos/services/cord/templates/vbngadmin.html
index 721f76c..cceaee0 100644
--- a/xos/services/cord/templates/vbngadmin.html
+++ b/xos/services/cord/templates/vbngadmin.html
@@ -1,6 +1,6 @@
-<div class = "left-nav">
-<ul>
-<li><a href="/admin/cord/vbngtenant/">vBNG Tenants</a></li>
-</ul>
+<div class = "row text-center">
+ <div class="col-xs-12">
+ <a class="btn btn-primary" href="/admin/cord/vbngtenant/">vBNG Tenants</a>
+ </div>
</div>
diff --git a/xos/services/cord/templates/vcpeadmin.html b/xos/services/cord/templates/vcpeadmin.html
index 7a5d43f..a21dabe 100644
--- a/xos/services/cord/templates/vcpeadmin.html
+++ b/xos/services/cord/templates/vcpeadmin.html
@@ -1,7 +1,9 @@
-<div class = "left-nav">
-<ul>
-<li><a href="/admin/cord/vcpetenant/">vCPE Tenants</a></li>
-<li><a href="/admin/dashboard/cord/">Subscriber View</a></li>
-</ul>
+<div class = "row text-center">
+ <div class="col-xs-6">
+ <a class="btn btn-primary" href="/admin/cord/vcpetenant/">vCPE Tenants</a>
+ </div>
+ <div class="col-xs-6">
+ <a class="btn btn-primary" href="/admin/dashboard/cord/">Subscriber View</a>
+ </div>
</div>
diff --git a/xos/services/cord/templates/voltadmin.html b/xos/services/cord/templates/voltadmin.html
index 5bf28ff..e6887c5 100644
--- a/xos/services/cord/templates/voltadmin.html
+++ b/xos/services/cord/templates/voltadmin.html
@@ -1,6 +1,6 @@
-<div class = "left-nav">
-<ul>
-<li><a href="/admin/cord/volttenant/">vOLT Tenants</a></li>
-</ul>
+<div class = "row text-center">
+ <div class="col-xs-12">
+ <a href="/admin/cord/volttenant/">vOLT Tenants</a>
+ </div>
</div>
diff --git a/xos/tools/xos-manage b/xos/tools/xos-manage
index 075870b..4783bf5 100755
--- a/xos/tools/xos-manage
+++ b/xos/tools/xos-manage
@@ -82,7 +82,7 @@
python $XOS_DIR/manage.py syncdb --noinput
if [[ $DJANGO_17 ]]; then
echo "Loading initial data from fixture..."
- python $XOS_DIR/manage.py --noobserver --nomodelpolicy loaddata $XOS_DIR/core/fixtures/initial_data.json
+ python $XOS_DIR/manage.py --noobserver --nomodelpolicy loaddata $XOS_DIR/core/fixtures/core_initial_data.json
fi
}
function evolvedb {
@@ -170,8 +170,8 @@
dumpdata
# TODO: This is where we could run migration scripts to upgrade the
# dumped data to the new models.
- mv $XOS_DIR/core/fixtures/initial_data.json $XOS_DIR/core/fixtures/initial_data.json-old
- cp $BACKUP_DIR/dumpdata-latest.json $XOS_DIR/core/fixtures/initial_data.json
+ mv $XOS_DIR/core/fixtures/core_initial_data.json $XOS_DIR/core/fixtures/core_initial_data.json-old
+ cp $BACKUP_DIR/dumpdata-latest.json $XOS_DIR/core/fixtures/core_initial_data.json
dropdb
createdb
syncdb
@@ -183,8 +183,8 @@
fi
stopserver
ensure_postgres_running
- mv $XOS_DIR/core/fixtures/initial_data.json $XOS_DIR/core/fixtures/initial_data.json-old
- cp $BACKUP_DIR/dumpdata-latest.json $XOS_DIR/core/fixtures/initial_data.json
+ mv $XOS_DIR/core/fixtures/core_initial_data.json $XOS_DIR/core/fixtures/core_initial_data.json-old
+ cp $BACKUP_DIR/dumpdata-latest.json $XOS_DIR/core/fixtures/core_initial_data.json
dropdb
createdb
syncdb