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