You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2017/12/30 11:40:19 UTC

[GitHub] DaanHoogland closed pull request #2301: CLOUDSTACK-10121 moveUser

DaanHoogland closed pull request #2301: CLOUDSTACK-10121 moveUser
URL: https://github.com/apache/cloudstack/pull/2301
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/api/pom.xml b/api/pom.xml
index 6352e117bfe..ce90eea4548 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -56,6 +56,11 @@
       <artifactId>cloud-framework-ca</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>${cs.commons-lang3.version}</version>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java
index 26b692205bf..b7454693a74 100644
--- a/api/src/com/cloud/event/EventTypes.java
+++ b/api/src/com/cloud/event/EventTypes.java
@@ -198,6 +198,7 @@
     public static final String EVENT_USER_CREATE = "USER.CREATE";
     public static final String EVENT_USER_DELETE = "USER.DELETE";
     public static final String EVENT_USER_DISABLE = "USER.DISABLE";
+    public static final String EVENT_USER_MOVE = "USER.MOVE";
     public static final String EVENT_USER_UPDATE = "USER.UPDATE";
     public static final String EVENT_USER_ENABLE = "USER.ENABLE";
     public static final String EVENT_USER_LOCK = "USER.LOCK";
diff --git a/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java b/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java
new file mode 100644
index 00000000000..b32aa2f1326
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java
@@ -0,0 +1,126 @@
+// 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 org.apache.cloudstack.api.command.admin.user;
+
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.google.common.base.Preconditions;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.region.RegionService;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+@APICommand(name = "moveUser",
+        description = "Moves a user to another account",
+        responseObject = SuccessResponse.class,
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false,
+        since = "4.11",
+        authorized = {RoleType.Admin})
+public class MoveUserCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(UpdateUserCmd.class.getName());
+
+    public static final String APINAME = "moveUser";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.ID,
+            type = CommandType.UUID,
+            entityType = UserResponse.class,
+            required = true,
+            description = "id of the user to be deleted")
+    private Long id;
+
+    @Parameter(name = ApiConstants.ACCOUNT,
+            type = CommandType.STRING,
+            description = "Creates the user under the specified account. If no account is specified, the username will be used as the account name.")
+    private String accountName;
+
+    @Parameter(name = ApiConstants.ACCOUNT_ID,
+            type = CommandType.UUID,
+            entityType = AccountResponse.class,
+            description = "Creates the user under the specified domain. Has to be accompanied with the account parameter")
+    private Long accountId;
+
+    @Inject
+    RegionService _regionService;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public Long getAccountId() {
+        return accountId;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        User user = _entityMgr.findById(User.class, getId());
+        if (user != null) {
+            return user.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
+    }
+
+    @Override
+    public void execute() {
+        Preconditions.checkNotNull(getId(),"I have to have an user to move!");
+        Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!");
+
+        CallContext.current().setEventDetails("UserId: " + getId());
+        boolean result =
+                _regionService.moveUser(this);
+        if (result) {
+            SuccessResponse response = new SuccessResponse(getCommandName());
+            this.setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the user to a new account");
+        }
+    }
+
+}
diff --git a/api/src/org/apache/cloudstack/region/RegionService.java b/api/src/org/apache/cloudstack/region/RegionService.java
index afefcc7672e..bee66910b4d 100644
--- a/api/src/org/apache/cloudstack/region/RegionService.java
+++ b/api/src/org/apache/cloudstack/region/RegionService.java
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
 import org.apache.cloudstack.api.command.admin.user.DisableUserCmd;
 import org.apache.cloudstack.api.command.admin.user.EnableUserCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 import org.apache.cloudstack.api.command.user.region.ListRegionsCmd;
 
@@ -110,10 +111,17 @@
      */
     boolean deleteUser(DeleteUserCmd deleteUserCmd);
 
+    /**
+     * Deletes user by Id
+     * @param moveUserCmd
+     * @return true if delete was successful, false otherwise
+     */
+    boolean moveUser(MoveUserCmd moveUserCmd);
+
     /**
      * update an existing domain
      *
-     * @param cmd
+     * @param updateDomainCmd
      *            - the command containing domainId and new domainName
      * @return Domain object if the command succeeded
      */
diff --git a/engine/schema/src/com/cloud/user/UserVO.java b/engine/schema/src/com/cloud/user/UserVO.java
index d6ddb587204..05655bf711e 100644
--- a/engine/schema/src/com/cloud/user/UserVO.java
+++ b/engine/schema/src/com/cloud/user/UserVO.java
@@ -127,7 +127,25 @@ public UserVO(long accountId, String username, String password, String firstName
         this.source = source;
     }
 
-    @Override
+    public UserVO(UserVO user) {
+        this.setAccountId(user.getAccountId());
+        this.setUsername(user.getUsername());
+        this.setPassword(user.getPassword());
+        this.setFirstname(user.getFirstname());
+        this.setLastname(user.getLastname());
+        this.setEmail(user.getEmail());
+        this.setTimezone(user.getTimezone());
+        this.setUuid(user.getUuid());
+        this.setSource(user.getSource());
+        this.setApiKey(user.getApiKey());
+        this.setSecretKey(user.getSecretKey());
+        this.setExternalEntity(user.getExternalEntity());
+        this.setRegistered(user.isRegistered());
+        this.setRegistrationToken(user.getRegistrationToken());
+        this.setState(user.getState());
+    }
+
+        @Override
     public long getId() {
         return id;
     }
diff --git a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index a6170131292..fa933244a66 100644
--- a/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ b/plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@ -25,6 +25,7 @@
 import javax.naming.ConfigurationException;
 
 import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.log4j.Logger;
 
@@ -316,6 +317,10 @@ public boolean deleteUser(DeleteUserCmd arg0) {
         return false;
     }
 
+    @Override public boolean moveUser(MoveUserCmd moveUserCmd) {
+        return false;
+    }
+
     @Override
     public boolean deleteUserAccount(long arg0) {
         // TODO Auto-generated method stub
diff --git a/pom.xml b/pom.xml
index 66cfd0992a2..bc76817c0d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,7 +94,7 @@
     <cs.aws.sdk.version>1.11.213</cs.aws.sdk.version>
     <cs.jackson.version>2.9.2</cs.jackson.version>
     <cs.lang.version>2.6</cs.lang.version>
-    <cs.commons-lang3.version>3.4</cs.commons-lang3.version>
+    <cs.commons-lang3.version>3.6</cs.commons-lang3.version>
     <cs.commons-io.version>2.6</cs.commons-io.version>
     <cs.commons-fileupload.version>1.3.3</cs.commons-fileupload.version>
     <cs.commons-collections.version>4.1</cs.commons-collections.version>
diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java
index 177342b0575..c855c34b60f 100644
--- a/server/src/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/com/cloud/server/ManagementServerImpl.java
@@ -229,6 +229,7 @@
 import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
 import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
 import org.apache.cloudstack.api.command.admin.user.LockUserCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
@@ -2672,6 +2673,7 @@ public long getMemoryOrCpuCapacityByHost(final Long hostId, final short capacity
         cmdList.add(GetUserCmd.class);
         cmdList.add(ListUsersCmd.class);
         cmdList.add(LockUserCmd.class);
+        cmdList.add(MoveUserCmd.class);
         cmdList.add(RegisterCmd.class);
         cmdList.add(UpdateUserCmd.class);
         cmdList.add(CreateVlanIpRangeCmd.class);
diff --git a/server/src/com/cloud/user/AccountManager.java b/server/src/com/cloud/user/AccountManager.java
index 9e0dde20868..e0e7d3bf12d 100644
--- a/server/src/com/cloud/user/AccountManager.java
+++ b/server/src/com/cloud/user/AccountManager.java
@@ -23,6 +23,7 @@
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 
 import com.cloud.api.query.vo.ControlledViewEntity;
@@ -155,10 +156,17 @@ void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> s
      */
     boolean deleteUser(DeleteUserCmd deleteUserCmd);
 
+    /**
+     * moves a user to another account within the same domain
+     * @param moveUserCmd
+     * @return true if the user was successfully moved
+     */
+    boolean moveUser(MoveUserCmd moveUserCmd);
+
     /**
      * Update a user by userId
      *
-     * @param userId
+     * @param cmd
      * @return UserAccount object
      */
     UserAccount updateUser(UpdateUserCmd cmd);
diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java
index 294bc6e84ef..aaaa92ba3c1 100644
--- a/server/src/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/com/cloud/user/AccountManagerImpl.java
@@ -52,6 +52,7 @@
 import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
 import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 import org.apache.cloudstack.context.CallContext;
@@ -1686,29 +1687,89 @@ public Boolean doInTransaction(TransactionStatus status) {
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_USER_DELETE, eventDescription = "deleting User")
     public boolean deleteUser(DeleteUserCmd deleteUserCmd) {
-        long id = deleteUserCmd.getId();
+        UserVO user = getValidUserVO(deleteUserCmd.getId());
 
-        UserVO user = _userDao.findById(id);
+        Account account = _accountDao.findById(user.getAccountId());
 
-        if (user == null) {
-            throw new InvalidParameterValueException("The specified user doesn't exist in the system");
+        // don't allow to delete the user from the account of type Project
+        checkAccountAndAccess(user, account);
+        return _userDao.remove(deleteUserCmd.getId());
+    }
+
+    @ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account")
+    public boolean moveUser(MoveUserCmd cmd) {
+        UserVO user = getValidUserVO(cmd.getId());
+        Account oldAccount = _accountDao.findById(user.getAccountId());
+        checkAccountAndAccess(user, oldAccount);
+        long domainId = oldAccount.getDomainId();
+
+        long newAccountId = getNewAccountId(cmd, domainId);
+
+        if(newAccountId == user.getAccountId()) {
+            // could do a not silent fail but the objective of the user is reached
+            return true; // no need to create a new user object for this user
         }
+        return Transaction.execute(new TransactionCallback<Boolean>() {
+            @Override
+            public Boolean doInTransaction(TransactionStatus status) {
+                UserVO newUser = new UserVO(user);
+                user.setExternalEntity(user.getUuid());
+                user.setUuid(UUID.randomUUID().toString());
+                _userDao.update(user.getId(),user);
+                newUser.setAccountId(newAccountId);
+                boolean success = _userDao.remove(cmd.getId());
+                UserVO persisted = _userDao.persist(newUser);
+                return success && persisted.getUuid().equals(user.getExternalEntity());
+            }
+        });
+    }
 
-        Account account = _accountDao.findById(user.getAccountId());
+    private long getNewAccountId(MoveUserCmd cmd, long domainId) {
+        Account newAccount = null;
+        if (StringUtils.isNotBlank(cmd.getAccountName())) {
+            if(s_logger.isDebugEnabled()) {
+                s_logger.debug("Getting id for account by name '" + cmd.getAccountName() + "' in domain " + domainId);
+            }
+            newAccount = _accountDao.findEnabledAccount(cmd.getAccountName(), domainId);
+        }
+        if (newAccount == null && cmd.getAccountId() != null) {
+            newAccount = _accountDao.findById(cmd.getAccountId());
+        }
+        if (newAccount == null) {
+            throw new CloudRuntimeException("no account name or account id. this should have been caught before this point");
+        }
+        long newAccountId = newAccount.getAccountId();
+
+        if(newAccount.getDomainId() != domainId) {
+            // not in scope
+            throw new InvalidParameterValueException("moving a user from an account in one domain to an account in annother domain is not supported!");
+        }
+        return newAccountId;
+    }
 
+    private void checkAccountAndAccess(UserVO user, Account account) {
         // don't allow to delete the user from the account of type Project
         if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
+            throw new InvalidParameterValueException("Project users cannot be deleted or moved.");
+        }
+
+        checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
+        CallContext.current().putContextParameter(User.class, user.getUuid());
+    }
+
+    private UserVO getValidUserVO(long id) {
+        UserVO user = _userDao.findById(id);
+
+        if (user == null || user.getRemoved() != null) {
             throw new InvalidParameterValueException("The specified user doesn't exist in the system");
         }
 
         // don't allow to delete default user (system and admin users)
         if (user.isDefault()) {
-            throw new InvalidParameterValueException("The user is default and can't be removed");
+            throw new InvalidParameterValueException("The user is default and can't be (re)moved");
         }
 
-        checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
-        CallContext.current().putContextParameter(User.class, user.getUuid());
-        return _userDao.remove(id);
+        return user;
     }
 
     protected class AccountCleanupTask extends ManagedContextRunnable {
diff --git a/server/src/org/apache/cloudstack/region/RegionManager.java b/server/src/org/apache/cloudstack/region/RegionManager.java
index 6f254817103..f7d7c10c5b9 100644
--- a/server/src/org/apache/cloudstack/region/RegionManager.java
+++ b/server/src/org/apache/cloudstack/region/RegionManager.java
@@ -21,6 +21,7 @@
 import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
 import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 
 import com.cloud.domain.Domain;
@@ -122,10 +123,17 @@
      */
     boolean deleteUser(DeleteUserCmd deleteUserCmd);
 
+    /**
+     * Deletes user by Id
+     * @param moveUserCmd
+     * @return
+     */
+    boolean moveUser(MoveUserCmd moveUserCmd);
+
     /**
      * update an existing domain
      *
-     * @param cmd
+     * @param updateDomainCmd
      *            - the command containing domainId and new domainName
      * @return Domain object if the command succeeded
      */
@@ -142,7 +150,7 @@
     /**
      * Update a user by userId
      *
-     * @param userId
+     * @param updateUserCmd
      * @return UserAccount object
      */
     UserAccount updateUser(UpdateUserCmd updateUserCmd);
@@ -150,7 +158,7 @@
     /**
      * Disables a user by userId
      *
-     * @param userId
+     * @param id
      *            - the userId
      * @return UserAccount object
      */
diff --git a/server/src/org/apache/cloudstack/region/RegionManagerImpl.java b/server/src/org/apache/cloudstack/region/RegionManagerImpl.java
index 7e7189e09ee..0878eef8e8a 100644
--- a/server/src/org/apache/cloudstack/region/RegionManagerImpl.java
+++ b/server/src/org/apache/cloudstack/region/RegionManagerImpl.java
@@ -24,6 +24,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -224,6 +225,14 @@ public boolean deleteUser(DeleteUserCmd cmd) {
         return _accountMgr.deleteUser(cmd);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean moveUser(MoveUserCmd cmd) {
+        return _accountMgr.moveUser(cmd);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/server/src/org/apache/cloudstack/region/RegionServiceImpl.java b/server/src/org/apache/cloudstack/region/RegionServiceImpl.java
index cd3a147a9ac..5afafffc5da 100644
--- a/server/src/org/apache/cloudstack/region/RegionServiceImpl.java
+++ b/server/src/org/apache/cloudstack/region/RegionServiceImpl.java
@@ -34,6 +34,7 @@
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
 import org.apache.cloudstack.api.command.admin.user.DisableUserCmd;
 import org.apache.cloudstack.api.command.admin.user.EnableUserCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
 import org.apache.cloudstack.api.command.user.region.ListRegionsCmd;
 
@@ -151,6 +152,14 @@ public boolean deleteUser(DeleteUserCmd cmd) {
         return _regionMgr.deleteUser(cmd);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean moveUser(MoveUserCmd cmd) {
+        return _regionMgr.moveUser(cmd);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/server/test/com/cloud/user/MockAccountManagerImpl.java b/server/test/com/cloud/user/MockAccountManagerImpl.java
index 9429c86a117..8ea0473aa36 100644
--- a/server/test/com/cloud/user/MockAccountManagerImpl.java
+++ b/server/test/com/cloud/user/MockAccountManagerImpl.java
@@ -23,6 +23,7 @@
 import javax.naming.ConfigurationException;
 
 import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.springframework.stereotype.Component;
 
@@ -122,6 +123,10 @@ public boolean deleteUser(DeleteUserCmd deleteUserCmd) {
         return false;
     }
 
+    @Override public boolean moveUser(MoveUserCmd moveUserCmd) {
+        return false;
+    }
+
     @Override
     public boolean isAdmin(Long accountId) {
         // TODO Auto-generated method stub
diff --git a/test/integration/smoke/test_accounts.py b/test/integration/smoke/test_accounts.py
index 00047bf5c2e..dffb00aaef4 100644
--- a/test/integration/smoke/test_accounts.py
+++ b/test/integration/smoke/test_accounts.py
@@ -2086,3 +2086,140 @@ def test_DeleteDomain(self):
             domain.delete(self.apiclient, cleanup=False)
         return
 
+class TestMoveUser(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestMoveUser, cls).getClsTestClient()
+        cls.api_client = cls.testClient.getApiClient()
+        cls.testdata = cls.testClient.getParsedTestDataConfig()
+
+        cls.domain = get_domain(cls.api_client)
+        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
+        cls.testdata['mode'] = cls.zone.networktype
+
+        cls.template = get_test_template(
+            cls.api_client,
+            cls.zone.id,
+            cls.testdata["ostype"]
+        )
+
+        cls.testdata["virtual_machine"]["zoneid"] = cls.zone.id
+        cls._cleanup = []
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            # Clean up, terminate the created resources
+            cleanup_resources(cls.api_client, cls._cleanup)
+        except Exception as e:
+
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+        self.testdata = self.testClient.getParsedTestDataConfig()
+        self.account1 = Account.create(
+            self.apiclient,
+            self.testdata["acl"]["accountD1"],
+            domainid=self.domain.id
+        )
+        self.cleanup.append(self.account1)
+
+        self.account2 = Account.create(
+            self.apiclient,
+            self.testdata["acl"]["accountD1A"],
+            domainid=self.domain.id
+        )
+        self.cleanup.append(self.account2)
+
+        self.user = User.create(
+            self.apiclient,
+            self.testdata["user"],
+            account=self.account1.name,
+            domainid=self.account1.domainid
+        )
+
+        return
+
+    def tearDown(self):
+        try:
+            # Clean up, terminate the created resources
+            cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false")
+    def test_move_user_to_accountID(self):
+
+        self.user.move(self.api_client, dest_accountid=self.account2.id)
+
+        self.assertEqual(
+            self.account2.name,
+            self.user.list(self.apiclient, id=self.user.id)[0].account,
+            "Check user source of created user"
+        )
+        return
+
+    @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false")
+    def test_move_user_to_account_name(self):
+
+        self.user.move(self.api_client, dest_account=self.account2.name)
+
+        self.assertEqual(
+            self.account2.name,
+            self.user.list(self.apiclient, id=self.user.id)[0].account,
+            "Check user source of created user"
+        )
+        return
+
+    @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false")
+    def test_move_user_to_different_domain(self):
+        domain2 = Domain.create(self.api_client,
+                                self.testdata["domain"],
+                                parentdomainid=self.domain.id
+                                )
+        self.cleanup.append(domain2)
+
+        account_different_domain = Account.create(
+            self.apiclient,
+            self.testdata["acl"]["accountD1B"],
+            domainid=domain2.id
+        )
+        self.cleanup.append(account_different_domain)
+        try:
+            self.user.move(self.api_client, dest_account=account_different_domain.name)
+        except Exception:
+            pass
+        else:
+            self.fail("It should not be allowed to move users across accounts in different domains, failing")
+
+        account_different_domain.delete(self.api_client)
+        return
+
+    @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false")
+    def test_move_user_incorrect_account_id(self):
+
+        try:
+            self.user.move(self.api_client, dest_accountid='incorrect-account-id')
+        except Exception:
+            pass
+        else:
+            self.fail("moving to non-existing account should not be possible, failing")
+        return
+
+    @attr(tags=["domains", "advanced", "advancedns", "simulator","dvs"], required_hardware="false")
+    def test_move_user_incorrect_account_name(self):
+
+        try:
+            self.user.move(self.api_client, dest_account='incorrect-account-name')
+        except Exception:
+            pass
+        else:
+            self.fail("moving to non-existing account should not be possible, failing")
+        return
diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py
index 64733f7e179..7e7a2fdb7de 100644
--- a/tools/marvin/marvin/config/test_data.py
+++ b/tools/marvin/marvin/config/test_data.py
@@ -71,6 +71,15 @@
         "username": "test-account2",
         "password": "password"
     },
+    "user": {
+         "email": "user@test.com",
+         "firstname": "User",
+         "lastname": "User",
+         "username": "User",
+           # Random characters are appended for unique
+           # username
+         "password": "fr3sca",
+     },
     "small": {
         "displayname": "testserver",
         "username": "root",
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index 4154e91cba2..f66a209bed7 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -267,6 +267,19 @@ def delete(self, apiclient):
         cmd.id = self.id
         apiclient.deleteUser(cmd)
 
+    def move(self, api_client, dest_accountid = None, dest_account = None, domain= None):
+
+        if all([dest_account, dest_accountid]) is None:
+            raise Exception("Please add either destination account or destination account ID.")
+
+        cmd = moveUser.moveUserCmd()
+        cmd.id = self.id
+        cmd.accountid = dest_accountid
+        cmd.account = dest_account
+        cmd.domain = domain
+
+        return api_client.moveUser(cmd)
+
     @classmethod
     def list(cls, apiclient, **kwargs):
         """Lists users and provides detailed account information for


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services