cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sw...@apache.org
Subject [3/9] git commit: updated refs/heads/master to bee2bdc
Date Wed, 11 May 2016 06:04:19 GMT
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/setup/db/db/schema-481to490-cleanup.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-481to490-cleanup.sql b/setup/db/db/schema-481to490-cleanup.sql
index b1bb702..ba20645 100644
--- a/setup/db/db/schema-481to490-cleanup.sql
+++ b/setup/db/db/schema-481to490-cleanup.sql
@@ -82,4 +82,192 @@ ALTER TABLE `cloud`.`vm_work_job` DROP INDEX `id` ;
 ALTER TABLE `cloud`.`vpc_gateways` DROP INDEX `id` ;
 ALTER TABLE `cloud`.`vpn_users` DROP INDEX `id` ;
 
+-- Dynamic roles changes
+DROP VIEW IF EXISTS `cloud`.`account_view`;
+CREATE VIEW `cloud`.`account_view` AS
+    select
+        account.id,
+        account.uuid,
+        account.account_name,
+        account.type,
+        account.role_id,
+        account.state,
+        account.removed,
+        account.cleanup_needed,
+        account.network_domain,
+        account.default,
+        domain.id domain_id,
+        domain.uuid domain_uuid,
+        domain.name domain_name,
+        domain.path domain_path,
+        data_center.id data_center_id,
+        data_center.uuid data_center_uuid,
+        data_center.name data_center_name,
+        account_netstats_view.bytesReceived,
+        account_netstats_view.bytesSent,
+        vmlimit.max vmLimit,
+        vmcount.count vmTotal,
+        runningvm.vmcount runningVms,
+        stoppedvm.vmcount stoppedVms,
+        iplimit.max ipLimit,
+        ipcount.count ipTotal,
+        free_ip_view.free_ip ipFree,
+        volumelimit.max volumeLimit,
+        volumecount.count volumeTotal,
+        snapshotlimit.max snapshotLimit,
+        snapshotcount.count snapshotTotal,
+        templatelimit.max templateLimit,
+        templatecount.count templateTotal,
+        vpclimit.max vpcLimit,
+        vpccount.count vpcTotal,
+        projectlimit.max projectLimit,
+        projectcount.count projectTotal,
+        networklimit.max networkLimit,
+        networkcount.count networkTotal,
+        cpulimit.max cpuLimit,
+        cpucount.count cpuTotal,
+        memorylimit.max memoryLimit,
+        memorycount.count memoryTotal,
+        primary_storage_limit.max primaryStorageLimit,
+        primary_storage_count.count primaryStorageTotal,
+        secondary_storage_limit.max secondaryStorageLimit,
+        secondary_storage_count.count secondaryStorageTotal,
+        async_job.id job_id,
+        async_job.uuid job_uuid,
+        async_job.job_status job_status,
+        async_job.account_id job_account_id
+    from
+        `cloud`.`free_ip_view`,
+        `cloud`.`account`
+            inner join
+        `cloud`.`domain` ON account.domain_id = domain.id
+            left join
+        `cloud`.`data_center` ON account.default_zone_id = data_center.id
+            left join
+        `cloud`.`account_netstats_view` ON account.id = account_netstats_view.account_id
+            left join
+        `cloud`.`resource_limit` vmlimit ON account.id = vmlimit.account_id
+            and vmlimit.type = 'user_vm'
+            left join
+        `cloud`.`resource_count` vmcount ON account.id = vmcount.account_id
+            and vmcount.type = 'user_vm'
+            left join
+        `cloud`.`account_vmstats_view` runningvm ON account.id = runningvm.account_id
+            and runningvm.state = 'Running'
+            left join
+        `cloud`.`account_vmstats_view` stoppedvm ON account.id = stoppedvm.account_id
+           and stoppedvm.state = 'Stopped'
+            left join
+        `cloud`.`resource_limit` iplimit ON account.id = iplimit.account_id
+            and iplimit.type = 'public_ip'
+            left join
+        `cloud`.`resource_count` ipcount ON account.id = ipcount.account_id
+            and ipcount.type = 'public_ip'
+            left join
+        `cloud`.`resource_limit` volumelimit ON account.id = volumelimit.account_id
+            and volumelimit.type = 'volume'
+            left join
+        `cloud`.`resource_count` volumecount ON account.id = volumecount.account_id
+            and volumecount.type = 'volume'
+            left join
+        `cloud`.`resource_limit` snapshotlimit ON account.id = snapshotlimit.account_id
+            and snapshotlimit.type = 'snapshot'
+            left join
+        `cloud`.`resource_count` snapshotcount ON account.id = snapshotcount.account_id
+            and snapshotcount.type = 'snapshot'
+            left join
+        `cloud`.`resource_limit` templatelimit ON account.id = templatelimit.account_id
+            and templatelimit.type = 'template'
+            left join
+        `cloud`.`resource_count` templatecount ON account.id = templatecount.account_id
+            and templatecount.type = 'template'
+            left join
+        `cloud`.`resource_limit` vpclimit ON account.id = vpclimit.account_id
+            and vpclimit.type = 'vpc'
+            left join
+        `cloud`.`resource_count` vpccount ON account.id = vpccount.account_id
+            and vpccount.type = 'vpc'
+            left join
+        `cloud`.`resource_limit` projectlimit ON account.id = projectlimit.account_id
+            and projectlimit.type = 'project'
+            left join
+        `cloud`.`resource_count` projectcount ON account.id = projectcount.account_id
+            and projectcount.type = 'project'
+            left join
+        `cloud`.`resource_limit` networklimit ON account.id = networklimit.account_id
+            and networklimit.type = 'network'
+            left join
+        `cloud`.`resource_count` networkcount ON account.id = networkcount.account_id
+            and networkcount.type = 'network'
+            left join
+        `cloud`.`resource_limit` cpulimit ON account.id = cpulimit.account_id
+            and cpulimit.type = 'cpu'
+            left join
+        `cloud`.`resource_count` cpucount ON account.id = cpucount.account_id
+            and cpucount.type = 'cpu'
+            left join
+        `cloud`.`resource_limit` memorylimit ON account.id = memorylimit.account_id
+            and memorylimit.type = 'memory'
+            left join
+        `cloud`.`resource_count` memorycount ON account.id = memorycount.account_id
+            and memorycount.type = 'memory'
+            left join
+        `cloud`.`resource_limit` primary_storage_limit ON account.id = primary_storage_limit.account_id
+            and primary_storage_limit.type = 'primary_storage'
+            left join
+        `cloud`.`resource_count` primary_storage_count ON account.id = primary_storage_count.account_id
+            and primary_storage_count.type = 'primary_storage'
+            left join
+        `cloud`.`resource_limit` secondary_storage_limit ON account.id = secondary_storage_limit.account_id
+            and secondary_storage_limit.type = 'secondary_storage'
+            left join
+        `cloud`.`resource_count` secondary_storage_count ON account.id = secondary_storage_count.account_id
+            and secondary_storage_count.type = 'secondary_storage'
+            left join
+        `cloud`.`async_job` ON async_job.instance_id = account.id
+            and async_job.instance_type = 'Account'
+            and async_job.job_status = 0;
 
+DROP VIEW IF EXISTS `cloud`.`user_view`;
+CREATE VIEW `cloud`.`user_view` AS
+    select
+        user.id,
+        user.uuid,
+        user.username,
+        user.password,
+        user.firstname,
+        user.lastname,
+        user.email,
+        user.state,
+        user.api_key,
+        user.secret_key,
+        user.created,
+        user.removed,
+        user.timezone,
+        user.registration_token,
+        user.is_registered,
+        user.incorrect_login_attempts,
+        user.default,
+        account.id account_id,
+        account.uuid account_uuid,
+        account.account_name account_name,
+        account.type account_type,
+        account.role_id account_role_id,
+        domain.id domain_id,
+        domain.uuid domain_uuid,
+        domain.name domain_name,
+        domain.path domain_path,
+        async_job.id job_id,
+        async_job.uuid job_uuid,
+        async_job.job_status job_status,
+        async_job.account_id job_account_id
+    from
+        `cloud`.`user`
+            inner join
+        `cloud`.`account` ON user.account_id = account.id
+            inner join
+        `cloud`.`domain` ON account.domain_id = domain.id
+            left join
+        `cloud`.`async_job` ON async_job.instance_id = user.id
+            and async_job.instance_type = 'User'
+            and async_job.job_status = 0;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/setup/db/db/schema-481to490.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-481to490.sql b/setup/db/db/schema-481to490.sql
index a02615f..a2622ed 100644
--- a/setup/db/db/schema-481to490.sql
+++ b/setup/db/db/schema-481to490.sql
@@ -436,3 +436,38 @@ VIEW `account_vmstats_view` AS
         (`vm_instance`.`vm_type` = 'User' and `vm_instance`.`removed` is NULL)
     GROUP BY `vm_instance`.`account_id` , `vm_instance`.`state`;
 -- End CLOUDSTACK-9340
+
+-- Dynamic roles
+CREATE TABLE IF NOT EXISTS `cloud`.`roles` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `uuid` varchar(255) UNIQUE,
+  `name` varchar(255) COMMENT 'unique name of the dynamic role',
+  `role_type` varchar(255) NOT NULL COMMENT 'the type of the role',
+  `removed` datetime COMMENT 'date removed',
+  `description` text COMMENT 'description of the role',
+  PRIMARY KEY (`id`),
+  KEY `i_roles__name` (`name`),
+  KEY `i_roles__role_type` (`role_type`),
+  UNIQUE KEY (`name`, `role_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `cloud`.`role_permissions` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `uuid` varchar(255) UNIQUE,
+  `role_id` bigint(20) unsigned NOT NULL COMMENT 'id of the role',
+  `rule` varchar(255) NOT NULL COMMENT 'rule for the role, api name or wildcard',
+  `permission` varchar(255) NOT NULL COMMENT 'access authority, allow or deny',
+  `description` text COMMENT 'description of the rule',
+  `sort_order` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT 'permission sort order',
+  PRIMARY KEY (`id`),
+  KEY `fk_role_permissions__role_id` (`role_id`),
+  KEY `i_role_permissions__sort_order` (`sort_order`),
+  UNIQUE KEY (`role_id`, `rule`),
+  CONSTRAINT `fk_role_permissions__role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Default CloudStack roles
+INSERT INTO `cloud`.`roles` (`id`, `uuid`, `name`, `role_type`, `description`) values (1, UUID(), 'Root Admin', 'Admin', 'Default root admin role') ON DUPLICATE KEY UPDATE name=name;
+INSERT INTO `cloud`.`roles` (`id`, `uuid`, `name`, `role_type`, `description`) values (2, UUID(), 'Resource Admin', 'ResourceAdmin', 'Default resource admin role') ON DUPLICATE KEY UPDATE name=name;
+INSERT INTO `cloud`.`roles` (`id`, `uuid`, `name`, `role_type`, `description`) values (3, UUID(), 'Domain Admin', 'DomainAdmin', 'Default domain admin role') ON DUPLICATE KEY UPDATE name=name;
+INSERT INTO `cloud`.`roles` (`id`, `uuid`, `name`, `role_type`, `description`) values (4, UUID(), 'User', 'User', 'Default Root Admin role') ON DUPLICATE KEY UPDATE name=name;

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/setup/db/server-setup.sql
----------------------------------------------------------------------
diff --git a/setup/db/server-setup.sql b/setup/db/server-setup.sql
index b7f5c3f..df2c924 100644
--- a/setup/db/server-setup.sql
+++ b/setup/db/server-setup.sql
@@ -25,4 +25,5 @@ INSERT INTO `cloud`.`domain` (id, uuid, name, parent, path, owner) VALUES (1, UU
 INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) VALUES ('Hidden', 'DEFAULT', 'none', 'init', null, null);
 -- INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) VALUES ('Advanced', 'DEFAULT', 'AgentManager', 'xenserver.public.network.device', 'public-network', "[OPTIONAL]The name of the XenServer network containing the physical network interface that is connected to the public network ");
 
-
+-- Enable dynamic RBAC by default for fresh deployments
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'RoleService', 'dynamic.apichecker.enabled', 'true');

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/setup/db/server-setup.xml
----------------------------------------------------------------------
diff --git a/setup/db/server-setup.xml b/setup/db/server-setup.xml
index bb8878f..178f29a 100755
--- a/setup/db/server-setup.xml
+++ b/setup/db/server-setup.xml
@@ -239,6 +239,13 @@ under the License.
       <value>qatest-vmops.com</value>
     </configuration>
     <!--
+    Enable Dynamic RBAC by default for fresh installations
+    -->
+    <configuration>
+      <name>dynamic.apichecker.enabled</name>
+      <value>true</value>
+    </configuration>
+    <!--
     The instance.name parameter is tacked to the end of the names of the VMs you create.
     So, for example, with the TEST value as it ships by default, your VMs would be named:
     i-X-Y-TEST, where X is the account ID and Y is the serially incrementing VM ID.

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/test/integration/smoke/test_dynamicroles.py
----------------------------------------------------------------------
diff --git a/test/integration/smoke/test_dynamicroles.py b/test/integration/smoke/test_dynamicroles.py
new file mode 100644
index 0000000..9961437
--- /dev/null
+++ b/test/integration/smoke/test_dynamicroles.py
@@ -0,0 +1,537 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from marvin.cloudstackAPI import *
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.lib.base import Account, Role, RolePermission
+from marvin.lib.utils import cleanup_resources
+from nose.plugins.attrib import attr
+from random import shuffle
+
+import copy
+import random
+import re
+
+
+class TestData(object):
+    """Test data object that is required to create resources
+    """
+    def __init__(self):
+        self.testdata = {
+            "account": {
+                "email": "mtu@test.cloud",
+                "firstname": "Marvin",
+                "lastname": "TestUser",
+                "username": "roletest",
+                "password": "password",
+            },
+            "role": {
+                "name": "MarvinFake Role ",
+                "type": "User",
+                "description": "Fake Role created by Marvin test"
+            },
+            "roleadmin": {
+                "name": "MarvinFake Admin Role ",
+                "type": "Admin",
+                "description": "Fake Admin Role created by Marvin test"
+            },
+            "roledomainadmin": {
+                "name": "MarvinFake DomainAdmin Role ",
+                "type": "DomainAdmin",
+                "description": "Fake Domain-Admin Role created by Marvin test"
+            },
+            "rolepermission": {
+                "roleid": 1,
+                "rule": "listVirtualMachines",
+                "permission": "allow",
+                "description": "Fake role permission created by Marvin test"
+            },
+            "apiConfig": {
+                "listApis": "allow",
+                "listAccounts": "allow",
+                "listClusters": "deny",
+                "*VM*": "allow",
+                "*Host*": "deny"
+            }
+        }
+
+
+class TestDynamicRoles(cloudstackTestCase):
+    """Tests dynamic role and role permission management in CloudStack
+    """
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.testdata = TestData().testdata
+
+        feature_enabled = self.apiclient.listCapabilities(listCapabilities.listCapabilitiesCmd()).dynamicrolesenabled
+        if not feature_enabled:
+            self.skipTest("Dynamic Role-Based API checker not enabled, skipping test")
+
+        self.testdata["role"]["name"] += self.getRandomString()
+        self.role = Role.create(
+            self.apiclient,
+            self.testdata["role"]
+        )
+
+        self.testdata["rolepermission"]["roleid"] = self.role.id
+        self.rolepermission = RolePermission.create(
+            self.apiclient,
+            self.testdata["rolepermission"]
+        )
+
+        self.account = Account.create(
+            self.apiclient,
+            self.testdata["account"],
+            roleid=self.role.id
+        )
+        self.cleanup = [
+            self.account,
+            self.rolepermission,
+            self.role
+        ]
+
+
+    def tearDown(self):
+        try:
+           cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            self.debug("Warning! Exception in tearDown: %s" % e)
+
+
+    def translateRoleToAccountType(self, role_type):
+        if role_type == "User":
+            return 0
+        elif role_type == "Admin":
+            return 1
+        elif role_type == "DomainAdmin":
+            return 2
+        elif role_type == "ResourceAdmin":
+            return 3
+        return -1
+
+
+    def getUserApiClient(self, username, domain='ROOT', role_type='User'):
+        self.user_apiclient = self.testClient.getUserApiClient(UserName=username, DomainName='ROOT', type=self.translateRoleToAccountType(role_type))
+        return self.user_apiclient
+
+
+    def getRandomString(self):
+        return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for _ in range(10))
+
+
+    def getRandomRoleName(self):
+        return "MarvinFakeRoleNewName-" + self.getRandomString()
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_lifecycle_list(self):
+        """
+            Tests that default four roles exist
+        """
+        roleTypes = {1: "Admin", 2: "ResourceAdmin", 3: "DomainAdmin", 4: "User"}
+        for idx in range(1,5):
+            list_roles = Role.list(self.apiclient, id=idx)
+            self.assertEqual(
+                isinstance(list_roles, list),
+                True,
+                "List Roles response was not a valid list"
+            )
+            self.assertEqual(
+                len(list_roles),
+                1,
+                "List Roles response size was not 1"
+            )
+            self.assertEqual(
+                list_roles[0].type,
+                roleTypes[idx],
+                msg="Default role type differs from expectation"
+            )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_lifecycle_create(self):
+        """
+            Tests normal lifecycle operations for roles
+        """
+        # Reuse self.role created in setUp()
+        try:
+            role = Role.create(
+                self.apiclient,
+                self.testdata["role"]
+            )
+            self.fail("An exception was expected when creating duplicate roles")
+        except CloudstackAPIException: pass
+
+        list_roles = Role.list(self.apiclient, id=self.role.id)
+        self.assertEqual(
+            isinstance(list_roles, list),
+            True,
+            "List Roles response was not a valid list"
+        )
+        self.assertEqual(
+            len(list_roles),
+            1,
+            "List Roles response size was not 1"
+        )
+        self.assertEqual(
+            list_roles[0].name,
+            self.testdata["role"]["name"],
+            msg="Role name does not match the test data"
+        )
+        self.assertEqual(
+            list_roles[0].type,
+            self.testdata["role"]["type"],
+            msg="Role type does not match the test data"
+        )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_lifecycle_update(self):
+        """
+            Tests role update
+        """
+        self.account.delete(self.apiclient)
+        new_role_name = self.getRandomRoleName()
+        self.role.update(self.apiclient, name=new_role_name, type='Admin')
+        update_role = Role.list(self.apiclient, id=self.role.id)[0]
+        self.assertEqual(
+            update_role.name,
+            new_role_name,
+            msg="Role name does not match updated role name"
+        )
+        self.assertEqual(
+            update_role.type,
+            'Admin',
+            msg="Role type does not match updated role type"
+        )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_lifecycle_update_role_inuse(self):
+        """
+            Tests role update when role is in use by an account
+        """
+        new_role_name = self.getRandomRoleName()
+        try:
+            self.role.update(self.apiclient, name=new_role_name, type='Admin')
+            self.fail("Updation of role type is not allowed when role is in use")
+        except CloudstackAPIException: pass
+
+        self.role.update(self.apiclient, name=new_role_name)
+        update_role = Role.list(self.apiclient, id=self.role.id)[0]
+        self.assertEqual(
+            update_role.name,
+            new_role_name,
+            msg="Role name does not match updated role name"
+        )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_lifecycle_delete(self):
+        """
+            Tests role update
+        """
+        self.account.delete(self.apiclient)
+        self.role.delete(self.apiclient)
+        list_roles = Role.list(self.apiclient, id=self.role.id)
+        self.assertEqual(
+            list_roles,
+            None,
+            "List Roles response should be empty"
+        )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_inuse_deletion(self):
+        """
+            Test to ensure role in use cannot be deleted
+        """
+        try:
+            self.role.delete(self.apiclient)
+            self.fail("Role with any account should not be allowed to be deleted")
+        except CloudstackAPIException: pass
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_default_role_deletion(self):
+        """
+            Test to ensure 4 default roles cannot be deleted
+        """
+        for idx in range(1,5):
+            cmd = deleteRole.deleteRoleCmd()
+            cmd.id = idx
+            try:
+                self.apiclient.deleteRole(cmd)
+                self.fail("Default role got deleted with id: " + idx)
+            except CloudstackAPIException: pass
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_rolepermission_lifecycle_list(self):
+        """
+            Tests listing of default role's permission
+        """
+        for idx in range(1,5):
+            list_rolepermissions = RolePermission.list(self.apiclient, roleid=idx)
+            self.assertEqual(
+                isinstance(list_rolepermissions, list),
+                True,
+                "List rolepermissions response was not a valid list"
+            )
+            self.assertTrue(
+                len(list_rolepermissions) > 0,
+                "List rolepermissions response was empty"
+            )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_rolepermission_lifecycle_create(self):
+        """
+            Tests creation of role permission
+        """
+        # Reuse self.rolepermission created in setUp()
+        try:
+            rolepermission = RolePermission.create(
+                self.apiclient,
+                self.testdata["rolepermission"]
+            )
+            self.fail("An exception was expected when creating duplicate role permissions")
+        except CloudstackAPIException: pass
+
+        list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id)
+        self.assertEqual(
+            isinstance(list_rolepermissions, list),
+            True,
+            "List rolepermissions response was not a valid list"
+        )
+        self.assertNotEqual(
+            len(list_rolepermissions),
+            0,
+            "List rolepermissions response was empty"
+        )
+        self.assertEqual(
+            list_rolepermissions[0].rule,
+            self.testdata["rolepermission"]["rule"],
+            msg="Role permission rule does not match the test data"
+        )
+        self.assertEqual(
+            list_rolepermissions[0].permission,
+            self.testdata["rolepermission"]["permission"],
+            msg="Role permission permission-type does not match the test data"
+        )
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_rolepermission_lifecycle_update(self):
+        """
+            Tests order updation of role permission
+        """
+        permissions = [self.rolepermission]
+        rules = ['list*', '*Vol*', 'listCapabilities']
+        for rule in rules:
+            data = copy.deepcopy(self.testdata["rolepermission"])
+            data['rule'] = rule
+            permission = RolePermission.create(
+                self.apiclient,
+                data
+            )
+            self.cleanup.append(permission)
+            permissions.append(permission)
+
+
+        def validate_permissions_list(permissions):
+            list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id)
+            self.assertEqual(
+                len(list_rolepermissions),
+                len(permissions),
+                msg="List of role permissions do not match created list of permissions"
+            )
+
+            for idx, rolepermission in enumerate(list_rolepermissions):
+                self.assertEqual(
+                    rolepermission.rule,
+                    permissions[idx].rule,
+                    msg="Rule permission don't match with expected item at the index"
+                )
+                self.assertEqual(
+                    rolepermission.permission,
+                    permissions[idx].permission,
+                    msg="Rule permission don't match with expected item at the index"
+                )
+
+        # Move last item to the top
+        rule = permissions.pop(len(permissions)-1)
+        permissions = [rule] + permissions
+        rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
+        validate_permissions_list(permissions)
+
+        # Move to the bottom
+        rule = permissions.pop(0)
+        permissions = permissions + [rule]
+        rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
+        validate_permissions_list(permissions)
+
+        # Random shuffles
+        for _ in range(3):
+            shuffle(permissions)
+            rule.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
+            validate_permissions_list(permissions)
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_rolepermission_lifecycle_concurrent_updates(self):
+        """
+            Tests concurrent order updation of role permission
+        """
+        permissions = [self.rolepermission]
+        rules = ['list*', '*Vol*', 'listCapabilities']
+        for rule in rules:
+            data = copy.deepcopy(self.testdata["rolepermission"])
+            data['rule'] = rule
+            permission = RolePermission.create(
+                self.apiclient,
+                data
+            )
+            self.cleanup.append(permission)
+            permissions.append(permission)
+
+
+        # The following rule is considered to be created by another mgmt server
+        data = copy.deepcopy(self.testdata["rolepermission"])
+        data['rule'] = "someRule*"
+        permission = RolePermission.create(
+            self.apiclient,
+            data
+        )
+        self.cleanup.append(permission)
+
+        shuffle(permissions)
+        try:
+            permission.update(self.apiclient, ruleorder=",".join(map(lambda x: x.id, permissions)))
+            self.fail("Reordering should fail in case of concurrent updates by other user")
+        except CloudstackAPIException: pass
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_rolepermission_lifecycle_delete(self):
+        """
+            Tests deletion of role permission
+        """
+        permission = self.cleanup.pop(1)
+        permission.delete(self.apiclient)
+        list_rolepermissions = RolePermission.list(self.apiclient, roleid=self.role.id)
+        self.assertEqual(
+            list_rolepermissions,
+            None,
+            "List rolepermissions response should be empty"
+        )
+
+
+    def checkApiAvailability(self, apiConfig, userApiClient):
+        """
+            Checks available APIs based on api map
+        """
+        response = userApiClient.listApis(listApis.listApisCmd())
+        allowedApis = map(lambda x: x.name, response)
+        for api in allowedApis:
+            for rule, perm in apiConfig.items():
+                if re.match(rule.replace('*', '.*'), api):
+                    if perm.lower() == 'allow':
+                        break
+                    else:
+                        self.fail('Denied API found to be allowed: ' + api)
+
+
+    def checkApiCall(self, apiConfig, userApiClient):
+        """
+            Performs actual API calls to verify API ACLs
+        """
+        list_accounts = userApiClient.listAccounts(listAccounts.listAccountsCmd())
+        self.assertEqual(
+            isinstance(list_accounts, list),
+            True,
+            "List accounts response was not a valid list"
+        )
+        self.assertNotEqual(
+            len(list_accounts),
+            0,
+            "List accounts response was empty"
+        )
+
+        # Perform actual API call for deny API
+        try:
+            userApiClient.listHosts(listHosts.listHostsCmd())
+            self.fail("API call succeeded which is denied for the role")
+        except CloudstackAPIException: pass
+
+        # Perform actual API call for API with no allow/deny rule
+        try:
+            userApiClient.listZones(listZones.listZonesCmd())
+            self.fail("API call succeeded which has no allow/deny rule for the role")
+        except CloudstackAPIException: pass
+
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_account_acls(self):
+        """
+            Test to check role, role permissions and account life cycles
+        """
+        apiConfig = self.testdata['apiConfig']
+        for api, perm in apiConfig.items():
+            testdata = self.testdata['rolepermission']
+            testdata['roleid'] = self.role.id
+            testdata['rule'] = api
+            testdata['permission'] = perm.lower()
+
+            RolePermission.create(
+                self.apiclient,
+                testdata
+            )
+
+        userApiClient = self.getUserApiClient(self.account.name, domain=self.account.domain, role_type=self.account.roletype)
+
+        # Perform listApis check
+        self.checkApiAvailability(apiConfig, userApiClient)
+
+        # Perform actual API call for allow API
+        self.checkApiCall(apiConfig, userApiClient)
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_role_account_acls_multiple_mgmt_servers(self):
+        """
+            Test for role-rule enforcement in case of multiple mgmt servers
+            Inserts rule directly in DB and checks expected behaviour
+        """
+        apiConfig = self.testdata["apiConfig"]
+        roleId = self.dbclient.execute("select id from roles where uuid='%s'" % self.role.id)[0][0]
+        sortOrder = 1
+        for rule, perm in apiConfig.items():
+            self.dbclient.execute("insert into role_permissions (uuid, role_id, rule, permission, sort_order) values (UUID(), %d, '%s', '%s', %d)" % (roleId, rule, perm.upper(), sortOrder))
+            sortOrder += 1
+
+        userApiClient = self.getUserApiClient(self.account.name, domain=self.account.domain, role_type=self.account.roletype)
+
+        # Perform listApis check
+        self.checkApiAvailability(apiConfig, userApiClient)
+
+        # Perform actual API call for allow API
+        self.checkApiCall(apiConfig, userApiClient)

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/test/integration/smoke/test_staticroles.py
----------------------------------------------------------------------
diff --git a/test/integration/smoke/test_staticroles.py b/test/integration/smoke/test_staticroles.py
new file mode 100644
index 0000000..5421f6b
--- /dev/null
+++ b/test/integration/smoke/test_staticroles.py
@@ -0,0 +1,134 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from marvin.cloudstackAPI import *
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.lib.base import Account
+from marvin.lib.utils import cleanup_resources
+from marvin.sshClient import SshClient
+from nose.plugins.attrib import attr
+
+import inspect
+import logging
+import os
+import re
+
+
+class TestStaticRoles(cloudstackTestCase):
+    """Tests static role api-checker
+    """
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__
+        self.cleanup = []
+        self.testdata = {
+            "account": {
+                "email": "mtu@test.cloud",
+                "firstname": "Marvin",
+                "lastname": "TestUser",
+                "username": "staticrole_acctest-",
+                "password": "password",
+            }
+        }
+
+        feature_enabled = self.apiclient.listCapabilities(listCapabilities.listCapabilitiesCmd()).dynamicrolesenabled
+        if feature_enabled:
+            self.skipTest("Dynamic role-based API checker is enabled, skipping tests for static role-base API checker")
+
+        commandsProperties = []
+        try:
+            sshClient = SshClient(
+                self.mgtSvrDetails["mgtSvrIp"],
+                22,
+                self.mgtSvrDetails["user"],
+                self.mgtSvrDetails["passwd"],
+                retries=1,
+                log_lvl=logging.INFO
+            )
+            result = sshClient.runCommand("cat /etc/cloudstack/management/commands.properties")
+            if 'status' in result and result['status'] == 'SUCCESS' and 'stdout' in result and len(result['stdout']) > 0:
+                commandsProperties = result['stdout']
+        except Exception:
+            self.debug("Failed to ssh into mgmt server host and grab commands.properties file")
+            testDir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+            localFileName = os.path.abspath(testDir + "/../../../client/tomcatconf/commands.properties.in")
+            if os.path.isfile(localFileName):
+                self.info("Detected that we're running in developer mode with maven, using file at:" + localFileName)
+                with open(localFileName) as f:
+                    commandsProperties = f.readlines()
+
+        if len(commandsProperties) < 1:
+            self.skipTest("Unable to find commands.properties, skipping this test")
+
+        apiMap = {}
+        for line in commandsProperties:
+            if not line or line == '' or line == '\n' or line.startswith('#'):
+                continue
+            name, value = line.split('=')
+            apiMap[name.strip()] = value.strip()
+
+        self.roleApiMap = {} # role to list of apis allowed
+        octetKey = {'Admin':1, 'DomainAdmin':4, 'User':8}
+        for role in octetKey.keys():
+            for api in sorted(apiMap.keys()):
+                if (octetKey[role] & int(apiMap[api])) > 0:
+                    if role not in self.roleApiMap:
+                        self.roleApiMap[role] = []
+                    self.roleApiMap[role].append(api)
+
+
+    def tearDown(self):
+        try:
+           cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            self.debug("Warning! Exception in tearDown: %s" % e)
+
+
+    def translateRoleToAccountType(self, role_type):
+        if role_type == 'User':
+            return 0
+        elif role_type == 'Admin':
+            return 1
+        elif role_type == 'DomainAdmin':
+            return 2
+        return -1
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_static_role_account_acls(self):
+        """
+            Tests allowed APIs for common account types
+        """
+        for role in ['Admin', 'DomainAdmin', 'User']:
+            accountType = self.translateRoleToAccountType(role)
+            account = Account.create(
+                self.apiclient,
+                self.testdata['account'],
+                admin=accountType
+            )
+            self.cleanup.append(account)
+            userApiClient = self.testClient.getUserApiClient(UserName=account.name, DomainName=account.domain, type=accountType)
+            allowedApis = map(lambda x: x.name, userApiClient.listApis(listApis.listApisCmd()))
+            allApis = map(lambda x: x.name, self.apiclient.listApis(listApis.listApisCmd()))
+            for api in self.roleApiMap[role]:
+                if api not in allApis:
+                    continue
+                if api not in allowedApis:
+                    self.fail("API configured in commands.properties not returned by listApis: " + api + " for role: " + role)

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/tools/apidoc/gen_toc.py
----------------------------------------------------------------------
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 6974fd7..9277fb4 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -73,6 +73,7 @@ known_categories = {
     'Host': 'Host',
     'Cluster': 'Cluster',
     'Account': 'Account',
+    'Role': 'Role',
     'Snapshot': 'Snapshot',
     'User': 'User',
     'Os': 'Guest OS',

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/tools/marvin/marvin/cloudstackConnection.py
----------------------------------------------------------------------
diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py
index aa7b18f..d044fdd 100644
--- a/tools/marvin/marvin/cloudstackConnection.py
+++ b/tools/marvin/marvin/cloudstackConnection.py
@@ -143,7 +143,7 @@ class CSConnection(object):
             ["=".join(
                 [str.lower(r[0]),
                  str.lower(
-                     urllib.quote_plus(str(r[1]))
+                     urllib.quote_plus(str(r[1]), safe="*")
                 ).replace("+", "%20")]
             ) for r in params]
         )

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/tools/marvin/marvin/lib/base.py
----------------------------------------------------------------------
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index 93923ad..f5f53a5 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -87,13 +87,96 @@ class Domain:
         return(apiclient.listDomains(cmd))
 
 
+class Role:
+    """Manage Role"""
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(cls, apiclient, services, domainid=None):
+        """Create role"""
+        cmd = createRole.createRoleCmd()
+        cmd.name = services["name"]
+        cmd.type = services["type"]
+        if "description" in services:
+            cmd.description = services["description"]
+
+        return Role(apiclient.createRole(cmd).__dict__)
+
+    def delete(self, apiclient):
+        """Delete Role"""
+
+        cmd = deleteRole.deleteRoleCmd()
+        cmd.id = self.id
+        apiclient.deleteRole(cmd)
+
+    def update(self, apiclient, **kwargs):
+        """Update the role"""
+
+        cmd = updateRole.updateRoleCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in kwargs.items()]
+        return apiclient.updateRole(cmd)
+
+    @classmethod
+    def list(cls, apiclient, **kwargs):
+        """List all Roles matching criteria"""
+
+        cmd = listRoles.listRolesCmd()
+        [setattr(cmd, k, v) for k, v in kwargs.items()]
+        return(apiclient.listRoles(cmd))
+
+
+class RolePermission:
+    """Manage Role Permission"""
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(cls, apiclient, services, domainid=None):
+        """Create role permission"""
+        cmd = createRolePermission.createRolePermissionCmd()
+        cmd.roleid = services["roleid"]
+        cmd.rule = services["rule"]
+        cmd.permission = services["permission"]
+        if "description" in services:
+            cmd.description = services["description"]
+
+        return RolePermission(apiclient.createRolePermission(cmd).__dict__)
+
+    def delete(self, apiclient):
+        """Delete role permission"""
+
+        cmd = deleteRolePermission.deleteRolePermissionCmd()
+        cmd.id = self.id
+        apiclient.deleteRolePermission(cmd)
+
+    def update(self, apiclient, **kwargs):
+        """Update the role permission"""
+
+        cmd = updateRolePermission.updateRolePermissionCmd()
+        cmd.roleid = self.roleid
+        [setattr(cmd, k, v) for k, v in kwargs.items()]
+        return apiclient.updateRolePermission(cmd)
+
+    @classmethod
+    def list(cls, apiclient, **kwargs):
+        """List all role permissions matching criteria"""
+
+        cmd = listRolePermissions.listRolePermissionsCmd()
+        [setattr(cmd, k, v) for k, v in kwargs.items()]
+        return(apiclient.listRolePermissions(cmd))
+
+
 class Account:
     """ Account Life Cycle """
     def __init__(self, items):
         self.__dict__.update(items)
 
     @classmethod
-    def create(cls, apiclient, services, admin=False, domainid=None):
+    def create(cls, apiclient, services, admin=False, domainid=None, roleid=None):
         """Creates an account"""
         cmd = createAccount.createAccountCmd()
 
@@ -121,6 +204,10 @@ class Account:
 
         if domainid:
             cmd.domainid = domainid
+
+        if roleid:
+            cmd.roleid = roleid
+
         account = apiclient.createAccount(cmd)
 
         return Account(account.__dict__)

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 830a390..17cab9e 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -2904,6 +2904,15 @@ div.detail-group.actions td {
   background-position: -366px -239px;
 }
 
+#navigation ul li.roles span.icon {
+  background-position: -460px -80px;
+}
+
+#navigation ul li.roles.active span.icon,
+#navigation ul li.roles:hover span.icon {
+  background-position: -469px -750px;
+}
+
 #navigation ul li.accounts span.icon {
   background-position: -458px -19px;
 }
@@ -13162,3 +13171,10 @@ div.gpugroups div.list-view {
   background: transparent url("../images/icons.png") no-repeat -626px -209px;
   padding: 0 0 3px 18px;
 }
+
+ul.ui-autocomplete.ui-menu {
+  width: 250px;
+  background: #ddd;
+  font-size: 13px;
+  padding: 5px;
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/dictionary.jsp
----------------------------------------------------------------------
diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp
index b97141c..ddcd181 100644
--- a/ui/dictionary.jsp
+++ b/ui/dictionary.jsp
@@ -1120,7 +1120,14 @@ dictionary = {
 'label.retry.interval': '<fmt:message key="label.retry.interval" />',
 'label.review': '<fmt:message key="label.review" />',
 'label.revoke.project.invite': '<fmt:message key="label.revoke.project.invite" />',
+'label.permission': '<fmt:message key="label.permission" />',
 'label.role': '<fmt:message key="label.role" />',
+'label.roles': '<fmt:message key="label.roles" />',
+'label.roletype': '<fmt:message key="label.roletype" />',
+'label.add.role': '<fmt:message key="label.add.role" />',
+'label.edit.role': '<fmt:message key="label.edit.role" />',
+'label.delete.role': '<fmt:message key="label.delete.role" />',
+'message.role.ordering.fail': '<fmt:message key="message.role.ordering.fail" />',
 'label.root.disk.controller': '<fmt:message key="label.root.disk.controller" />',
 'label.root.disk.offering': '<fmt:message key="label.root.disk.offering" />',
 'message.configure.firewall.rules.allow.traffic': '<fmt:message key="message.configure.firewall.rules.allow.traffic" />',

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/dictionary2.jsp
----------------------------------------------------------------------
diff --git a/ui/dictionary2.jsp b/ui/dictionary2.jsp
index 4268104..f919b15 100644
--- a/ui/dictionary2.jsp
+++ b/ui/dictionary2.jsp
@@ -26,6 +26,7 @@ under the License.
 <script type="text/javascript">
  $.extend(dictionary, {
 'label.add.ldap.account': '<fmt:message key="label.add.ldap.account" />',
+'label.rule': '<fmt:message key="label.rule" />',
 'label.rules': '<fmt:message key="label.rules" />',
 'label.running.vms': '<fmt:message key="label.running.vms" />',
 'label.s3.access_key': '<fmt:message key="label.s3.access_key" />',

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/images/icons.png
----------------------------------------------------------------------
diff --git a/ui/images/icons.png b/ui/images/icons.png
index fd1049d..f676969 100644
Binary files a/ui/images/icons.png and b/ui/images/icons.png differ

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/index.jsp
----------------------------------------------------------------------
diff --git a/ui/index.jsp b/ui/index.jsp
index 703507f..34f6310 100644
--- a/ui/index.jsp
+++ b/ui/index.jsp
@@ -1823,6 +1823,7 @@
         <script type="text/javascript" src="scripts/ui-custom/uploadVolume.js"></script>
         <script type="text/javascript" src="scripts/storage.js"></script>
         <script type="text/javascript" src="scripts/templates.js"></script>
+        <script type="text/javascript" src="scripts/roles.js"></script>
         <script type="text/javascript" src="scripts/accountsWizard.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/accountsWizard.js"></script>
         <script type="text/javascript" src="scripts/accounts.js"></script>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/plugins/quota/quota.js
----------------------------------------------------------------------
diff --git a/ui/plugins/quota/quota.js b/ui/plugins/quota/quota.js
index 4780d71..4505b96 100644
--- a/ui/plugins/quota/quota.js
+++ b/ui/plugins/quota/quota.js
@@ -25,12 +25,18 @@
           id: 'quota',
           title: 'Quota',
           preFilter: function(args) {
-    	        var retval = $.ajax({
-                	url: createURL("quotaIsEnabled"),
-                	async: false
+                var pluginEnabled = false;
+                $.ajax({
+                    url: createURL("quotaIsEnabled"),
+                    async: false,
+                    success: function(json) {
+                        pluginEnabled = json.quotaisenabledresponse.isenabled.isenabled;
+                    },
+                    error: function(data) {
+                        pluginEnabled = false;
+                    }
                 });
-    	        var json = JSON.parse(retval.responseText);
-    	        return json.quotaisenabledresponse.isenabled.isenabled;
+                return pluginEnabled;
           },
           showOnNavigation: true,
           sectionSelect: {

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/scripts/accounts.js
----------------------------------------------------------------------
diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js
index 0058b69..913f81f 100644
--- a/ui/scripts/accounts.js
+++ b/ui/scripts/accounts.js
@@ -17,6 +17,7 @@
 (function(cloudStack) {
 
     var domainObjs;
+    var roleObjs;
 
     cloudStack.sections.accounts = {
         title: 'label.accounts',
@@ -38,11 +39,11 @@
                         name: {
                             label: 'label.name'
                         },
-                        accounttype: {
-                            label: 'label.role',
-                            converter: function(args) {
-                                return cloudStack.converters.toRole(args);
-                            }
+                        rolename: {
+                            label: 'label.role'
+                        },
+                        roletype: {
+                            label: 'label.roletype'
                         },
                         domain: {
                             label: 'label.domain'
@@ -678,11 +679,11 @@
                                     id: {
                                         label: 'label.id'
                                     },
-                                    accounttype: {
-                                        label: 'label.role',
-                                        converter: function(args) {
-                                            return cloudStack.converters.toRole(args);
-                                        }
+                                    rolename: {
+                                        label: 'label.role'
+                                    },
+                                    roletype: {
+                                        label: 'label.roletype'
                                     },
                                     domain: {
                                         label: 'label.domain'
@@ -1570,11 +1571,11 @@
                                     account: {
                                         label: 'label.account.name'
                                     },
-                                    accounttype: {
-                                        label: 'label.role',
-                                        converter: function(args) {
-                                            return cloudStack.converters.toRole(args);
-                                        }
+                                    rolename: {
+                                        label: 'label.role'
+                                    },
+                                    roletype: {
+                                        label: 'label.roletype'
                                     },
                                     domain: {
                                         label: 'label.domain'

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/scripts/accountsWizard.js
----------------------------------------------------------------------
diff --git a/ui/scripts/accountsWizard.js b/ui/scripts/accountsWizard.js
index 633821e..7fc4014 100644
--- a/ui/scripts/accountsWizard.js
+++ b/ui/scripts/accountsWizard.js
@@ -110,24 +110,28 @@
                     required: false
                 }
             },
-            accounttype: {
-                label: 'label.type',
+            roleid: {
+                label: 'label.role',
                 docID: 'helpAccountType',
                 validation: {
                     required: true
                 },
                 select: function(args) {
-                    var items = [];
-                    items.push({
-                        id: 0,
-                        description: "User"
-                    }); //regular-user
-                    items.push({
-                        id: 1,
-                        description: "Admin"
-                    }); //root-admin
-                    args.response.success({
-                        data: items
+                    $.ajax({
+                        url: createURL("listRoles"),
+                        success: function(json) {
+                            var items = [];
+                            roleObjs = json.listrolesresponse.role;
+                            $(roleObjs).each(function() {
+                                items.push({
+                                    id: this.id,
+                                    description: this.name + ' (' + this.type + ')'
+                                });
+                            });
+                            args.response.success({
+                                data: items
+                            });
+                        }
                     });
                 }
             },
@@ -226,13 +230,9 @@
                 array1.push("&account=" + account);
             }
 
-            var accountType = args.data.accounttype;
-            if (accountType == "1") { //if "admin" is selected in account type dropdown
-                if (rootDomainId == undefined || args.data.domainid != rootDomainId ) { //but current login has no visibility to root domain object, or the selected domain is not root domain
-                    accountType = "2"; // change accountType from root-domain("1") to domain-admin("2")
-                }
+            if (args.data.roleid) {
+                array1.push("&roleid=" + args.data.roleid);
             }
-            array1.push("&accounttype=" + accountType);
 
             if (args.data.timezone !== null && args.data.timezone.length > 0) {
                 array1.push("&timezone=" + args.data.timezone);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/scripts/cloudStack.js
----------------------------------------------------------------------
diff --git a/ui/scripts/cloudStack.js b/ui/scripts/cloudStack.js
index 0725bf3..b712d6c 100644
--- a/ui/scripts/cloudStack.js
+++ b/ui/scripts/cloudStack.js
@@ -22,7 +22,7 @@
             var sections = [];
 
             if (isAdmin()) {
-                sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "domains", "events", "system", "global-settings", "configuration", "projects", "regions", "affinityGroups"];
+                sections = ["dashboard", "instances", "storage", "network", "templates", "roles", "accounts", "domains", "events", "system", "global-settings", "configuration", "projects", "regions", "affinityGroups"];
             } else if (isDomainAdmin()) {
                 sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "domains", "events", "projects", "configuration", "regions", "affinityGroups"];
             } else if (g_userProjectsEnabled) {
@@ -52,6 +52,7 @@
             templates: {},
             events: {},
             projects: {},
+            roles: {},
             accounts: {},
 
             domains: {}, //domain-admin and root-admin only

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/scripts/roles.js
----------------------------------------------------------------------
diff --git a/ui/scripts/roles.js b/ui/scripts/roles.js
new file mode 100644
index 0000000..eae088f
--- /dev/null
+++ b/ui/scripts/roles.js
@@ -0,0 +1,388 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+(function($, cloudStack) {
+    var apiList = [];
+    var rolePermissions = [];
+    cloudStack.sections.roles = {
+        title: 'label.roles',
+        id: 'roles',
+        listView: {
+            id: 'roles',
+            fields: {
+                name: {
+                    label: 'label.name'
+                },
+                type: {
+                    label: 'label.type'
+                },
+                description: {
+                    label: 'label.description'
+                }
+            },
+            disableInfiniteScrolling: true,
+            dataProvider: function(args) {
+                var data = {};
+                if (args.filterBy.search && args.filterBy.search.value) {
+                    data['name'] = args.filterBy.search.value;
+                }
+                $.ajax({
+                    url: createURL("listRoles"),
+                    data: data,
+                    dataType: "json",
+                    async: true,
+                    success: function(json) {
+                        var jsonObj;
+                        jsonObj = json.listrolesresponse.role;
+                        args.response.success({
+                            data: jsonObj
+                        });
+                    }
+                });
+            },
+            actions: {
+                add: {
+                    label: 'label.add.role',
+                    preFilter: function(args) {
+                        if (isAdmin())
+                            return true;
+                    },
+                    messages: {
+                        notification: function() {
+                            return 'label.add.role';
+                        }
+                    },
+                    createForm: {
+                        title: 'label.add.role',
+                        fields: {
+                            name: {
+                                label: 'label.name',
+                                validation: {
+                                    required: true
+                                }
+                            },
+                            description: {
+                                label: 'label.description',
+                            },
+                            type: {
+                                label: 'label.type',
+                                validation: {
+                                    required: true
+                                },
+                                select: function(args) {
+                                    var items = [];
+                                    items.push({
+                                        id: "Admin",
+                                        description: "Admin"
+                                    });
+                                    items.push({
+                                        id: "DomainAdmin",
+                                        description: "Domain Admin"
+                                    });
+                                    items.push({
+                                        id: "User",
+                                        description: "User"
+                                    });
+                                    args.response.success({
+                                        data: items
+                                    });
+                                }
+                            }
+                        }
+                    },
+                    action: function(args) {
+                        $.ajax({
+                            url: createURL('createRole'),
+                            data: args.data,
+                            success: function(json) {
+                                var item = json.createroleresponse.role;
+                                args.response.success({
+                                    data: item
+                                });
+                            },
+                            error: function(json) {
+                                args.response.error(parseXMLHttpResponse(json));
+                            }
+                        });
+                    },
+                    notification: {
+                        poll: function(args) {
+                            args.complete();
+                        }
+                    }
+                }
+            },
+            detailView: {
+                tabs: {
+                    details: {
+                        title: 'label.details',
+                        fields: {
+                            id: {
+                                label: 'label.id'
+                            },
+                            name: {
+                                label: 'label.name',
+                                isEditable: true,
+                                validation: {
+                                    required: true
+                                }
+                            },
+                            type: {
+                                label: 'label.type'
+                            },
+                            description: {
+                                label: 'label.description',
+                                isEditable: true
+                            }
+                        },
+                        dataProvider: function(args) {
+                            $.ajax({
+                                url: createURL("listRoles&id=" + args.context.roles[0].id),
+                                dataType: "json",
+                                async: true,
+                                success: function(json) {
+                                    var response = json.listrolesresponse.role[0];
+                                    args.response.success({
+                                        data: response
+                                    });
+                                }
+                            });
+                        }
+                    },
+                    rules: {
+                        title: 'label.rules',
+                        custom: function(args) {
+                            var context = args.context;
+
+                            return $('<div>').multiEdit({
+                                context: context,
+                                noSelect: true,
+                                noHeaderActionsColumn: true,
+                                reorder: {
+                                    moveDrag: {
+                                        action: function(args) {
+                                            var rule = args.context.multiRule[0];
+                                            var prevItemId = args.prevItem ? args.prevItem.id : 0;
+
+                                            var ruleOrder = [];
+                                            $.each(rolePermissions, function(idx, item) {
+                                                var itemId = item.id;
+                                                if (idx == 0 && prevItemId == 0) {
+                                                    ruleOrder.push(rule.id);
+                                                }
+                                                if (itemId == rule.id) {
+                                                    return true;
+                                                }
+                                                ruleOrder.push(item.id);
+                                                if (prevItemId == itemId) {
+                                                    ruleOrder.push(rule.id);
+                                                }
+                                            });
+
+                                            $.ajax({
+                                                url: createURL('updateRolePermission'),
+                                                data: {
+                                                    roleid: rule.roleid,
+                                                    ruleorder: ruleOrder.join()
+                                                },
+                                                success: function(json) {
+                                                    args.response.success();
+                                                    $(window).trigger('cloudStack.fullRefresh');
+                                                },
+                                                error: function(json) {
+                                                    cloudStack.dialog.notice({
+                                                        message: 'message.role.ordering.fail'
+                                                    });
+                                                }
+                                            });
+                                        }
+                                    }
+                                },
+                                fields: {
+                                    'rule': {
+                                        edit: true,
+                                        label: 'label.rule',
+                                        isOptional: false
+                                    },
+                                    'permission': {
+                                        label: 'label.permission',
+                                        select: function(args) {
+                                            args.response.success({
+                                                data: [{
+                                                    name: 'allow',
+                                                    description: 'Allow'
+                                                }, {
+                                                    name: 'deny',
+                                                    description: 'Deny'
+                                                }]
+                                            });
+                                        }
+                                    },
+                                    'description': {
+                                        edit: true,
+                                        label: 'label.description',
+                                        isOptional: true
+                                    },
+                                    'always-hide': {
+                                        label: 'label.action',
+                                        addButton: true
+                                    }
+                                },
+                                add: {
+                                    label: 'label.add',
+                                    action: function(args) {
+                                        var data = {
+                                            rule: args.data.rule,
+                                            permission: args.data.permission,
+                                            description: args.data.description,
+                                            roleid: args.context.roles[0].id
+                                        };
+
+                                        $.ajax({
+                                            url: createURL('createRolePermission'),
+                                            data: data,
+                                            dataType: 'json',
+                                            success: function(json) {
+                                                var response = json.createrolepermissionresponse.rolepermission;
+                                                args.response.success({
+                                                    data: response
+                                                });
+                                            },
+                                            error: function(json) {
+                                                args.response.error(parseXMLHttpResponse(json));
+                                            }
+                                        });
+                                    }
+                                },
+                                actions: {
+                                    destroy: {
+                                        label: 'label.remove.rule',
+                                        action: function(args) {
+                                            $.ajax({
+                                                url: createURL('deleteRolePermission'),
+                                                data: {
+                                                    id: args.context.multiRule[0].id
+                                                },
+                                                dataType: 'json',
+                                                success: function(data) {
+                                                    args.response.success();
+                                                },
+                                                error: function(json) {
+                                                    args.response.error(parseXMLHttpResponse(json));
+                                                }
+                                            });
+                                        }
+                                    }
+                                },
+                                dataProvider: function(args) {
+                                    $.ajax({
+                                        url: createURL('listRolePermissions'),
+                                        data: {
+                                            roleid: args.context.roles[0].id
+                                        },
+                                        dataType: 'json',
+                                        success: function(json) {
+                                            var rules = json.listrolepermissionsresponse.rolepermission;
+                                            if (rules) {
+                                                rolePermissions = rules;
+                                            }
+                                            args.response.success({
+                                                data: rules
+                                            });
+                                        }
+                                    });
+                                    var setupAutocompletion = function() {
+                                        $($.find('input[name="rule"]')).autocomplete("destroy");
+                                        $($.find('input[name="rule"]')).autocomplete({
+                                            source: apiList,
+                                            autoFocus:true
+                                        });
+                                    };
+                                    if (apiList.length == 0) {
+                                        $.ajax({
+                                            url: createURL("listApis"),
+                                            dataType: "json",
+                                            success: function(json) {
+                                                var response = json.listapisresponse.api;
+                                                $.each(response, function(idx, api) {
+                                                    apiList.push(api.name);
+                                                });
+                                                setupAutocompletion();
+                                            }
+                                        });
+                                    } else {
+                                        setupAutocompletion();
+                                    }
+                                }
+                            });
+                        }
+                    }
+                },
+                actions: {
+                    edit: {
+                        label: 'label.edit.role',
+                        action: function(args) {
+                            var data = {
+                                id: args.context.roles[0].id,
+                                name: args.data.name,
+                                description: args.data.description
+                            };
+
+                            $.ajax({
+                                url: createURL('updateRole'),
+                                data: data,
+                                success: function(json) {
+                                    args.response.success();
+                                },
+                                error: function(json) {
+                                    args.response.error(parseXMLHttpResponse(json));
+                                }
+                            });
+                        }
+                    },
+                    remove: {
+                        label: 'label.delete.role',
+                        messages: {
+                            confirm: function(args) {
+                                return 'label.delete.role';
+                            },
+                            notification: function(args) {
+                                return 'label.delete.role';
+                            }
+                        },
+                        action: function(args) {
+                            $.ajax({
+                                url: createURL("deleteRole&id=" + args.context.roles[0].id),
+                                dataType: "json",
+                                success: function(json) {
+                                    var response = json.deleteroleresponse;
+                                    args.response.success({
+                                        data: response
+                                    });
+                                }
+                            });
+                        },
+                        notification: {
+                            poll: function(args) {
+                                args.complete();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+})(jQuery, cloudStack);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/ui/scripts/sharedFunctions.js
----------------------------------------------------------------------
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 7b4b26a..88d728e 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -1112,6 +1112,17 @@ cloudStack.converters = {
             return "Domain-Admin";
         }
     },
+    toAccountType: function(roleType) {
+        if (roleType == 'User') {
+            return 0;
+        } else if (roleType == 'Admin') {
+            return 1;
+        } else if (roleType == 'DomainAdmin') {
+            return 2;
+        } else if (roleType == 'ResourceAdmin') {
+            return 3;
+        }
+    },
     toAlertType: function(alertCode) {
         switch (alertCode) {
             case 0:

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/utils/src/main/java/com/cloud/utils/ListUtils.java
----------------------------------------------------------------------
diff --git a/utils/src/main/java/com/cloud/utils/ListUtils.java b/utils/src/main/java/com/cloud/utils/ListUtils.java
new file mode 100644
index 0000000..fa9332f
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/ListUtils.java
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ListUtils {
+    public static <T> List<T> toListOfInterface(final List<? extends T> items) {
+        if (items != null) {
+            return new ArrayList<>(items);
+        }
+        return Collections.emptyList();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4347776a/utils/src/main/java/com/cloud/utils/PropertiesUtil.java
----------------------------------------------------------------------
diff --git a/utils/src/main/java/com/cloud/utils/PropertiesUtil.java b/utils/src/main/java/com/cloud/utils/PropertiesUtil.java
index 4cb89f7..c0da87a 100644
--- a/utils/src/main/java/com/cloud/utils/PropertiesUtil.java
+++ b/utils/src/main/java/com/cloud/utils/PropertiesUtil.java
@@ -34,6 +34,10 @@ import org.apache.log4j.Logger;
 public class PropertiesUtil {
     private static final Logger s_logger = Logger.getLogger(PropertiesUtil.class);
 
+    public static String getDefaultApiCommandsFileName() {
+        return "commands.properties";
+    }
+
     /**
      * Searches the class path and local paths to find the config file.
      * @param path path to find.  if it starts with / then it's absolute path.


Mime
View raw message