https://issues.apache.org/jira/browse/CLOUDSTACK-4992
4.4
Currently, under the environment of cloudstack with multiple regions, each region has its own management server running with a separate database. So if we want to support multiple regions and provide one point of entry for a customer, we need to duplicate domain/account/user information of that customer to all of the databases of regions the customer accesses, which will cause data discrepancies when users update those data independently in each management server.
I'd like to provide a way to sync up the data using the messaging system introduced in 4.1.0. Using the events from each management server, updates from each region can be propagated to the rest regions and they can be executed accordingly.
All management servers allow manual changes and any change in any server will be propagated to the rest of servers.
A new class that extends EventBus class to register domain/account/user event subscribers.
public class MultiRegionEventBus extends RabbitMQEventBus
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException
EventTopic topic = new EventTopic("ActionEvent", "*", "Domain", "*", "*");
subscribe(topic, new DomainSubscriber(1));......
EventTopic topic = new EventTopic("ActionEvent", "*", "Account", "*", "*");
subscribe(topic, new AccountSubscriber(2));.....
EventTopic topic = new EventTopic("ActionEvent", "*", "User", "*", "*");
subscribe(topic, new UserSubscriber(3));
......
A new class that extends EventSubscriber class to process events of domain/account/user.
public class MultiRegionSubscriber implements EventSubscriber
protected DomainDao domainDao;
protected AccountDao accountDao;
protected UserDao userDao;
protected boolean isCompleted(String status)
return (status != null && status.equals("Completed"));
protected boolean isExecutable()
String status = this.descMap.get("status");
if (!isCompleted(status)) return false;String entityUUID = this.descMap.get("entityuuid");
if (entityUUID == null || entityUUID.equals(""))
return false;
return true;
public class DomainSubscriber extends MultiRegionSubscriber
@Override
public void onEvent(Event event)
super.onEvent(event);
if (!isExecutable()) return;
process(event);
protected void process(Event event)
String entityUUID = this.descMap.get("entityuuid");
String oldDomainName = this.descMap.get("oldentityname");
Domain domain = this.domainDao.findByUuidIncludingRemoved(entityUUID);
Domain parentDomain = this.domainDao.findByIdIncludingRemoved(domain.getParent());.......
String methodName = event.getEventType().split("-")[1].toLowerCase();
DomainService domainService = new DomainService(hostName, userName, password);
Method method = domainService.getClass().getMethod(methodName, Domain.class, Domain.class, String.class);
method.invoke(domainService, domain, parentDomain, oldDomainName);
public class AccountSubscriber extends MultiRegionSubscriber
@Override
public void onEvent(Event event)
super.onEvent(event);
if (!isExecutable()) return;
process(event);
protected void process(Event event)
String entityUUID = this.descMap.get("entityuuid");
String oldAccountName = this.descMap.get("oldentityname");
User user = null;
Account account = this.accountDao.findByUuidIncludingRemoved(entityUUID);
Domain domain = this.domainDao.findByIdIncludingRemoved(account.getDomainId());
List<UserVO> users = this.userDao.listByAccount(account.getAccountId());
if (users.size() > 0) user = users.get(0);...............
String methodName = event.getEventType().split("-")[1].toLowerCase();
AccountService accountService = new AccountService(hostName, userName, password);
Method method = accountService.getClass().getMethod(methodName, User.class, Account.class, Domain.class, String.class);
method.invoke(accountService, user, account, domain, oldAccountName);
public class UserSubscriber extends MultiRegionSubscriber
@Override
public void onEvent(Event event)
super.onEvent(event);
if (!isExecutable()) return;
process(event);
protected void process(Event event)
String entityUUID = this.descMap.get("entityuuid");
String oldUserName = this.descMap.get("oldentityname");
User user = this.userDao.findByUuidIncludingRemoved(entityUUID);
Account account = this.accountDao.findByIdIncludingRemoved(user.getAccountId());
Domain domain = this.domainDao.findByIdIncludingRemoved(account.getDomainId());...............
String methodName = event.getEventType().split("-")[1].toLowerCase();
UserService userService = new UserService(hostName, userName, password);
Method method = userService.getClass().getMethod(methodName, User.class, Account.class, Domain.class, String.class);
method.invoke(userService, user, account, domain, oldUserName);
A new class to send requests to remote regions using cloudstack API
To resolve the conflicts among the resources, below fields are added
For events to provide details of resources that have been created/deleted, their names along with those of parents will be added in the description of event. (the portion underlined are newly added)
Successfully completed creating Domain. Domain Name: domain1, Parent DomainId :1, Domain Path:domain_path
Successfully completed creating Account. Account Name: account1, Domain Id:8, Domain Path:domain_path
Successfully completed creating User. User Name: user1, FirstName :first, LastName: last, Account Name: account1, Domain Path:domain_path
DELETE Event
Successfully completed deleting Domain. Domain Id: 2, Domain Path:domain_path
Successfully completed deleting account. Account Id: 4, Account Name: account1, Domain Path:domain_path
Successfully completed deleting User. UserId: 6, User Name: user1, Account Name: account1, Domain Path:domain_path
Process this below routines in each region
current_region = where this routine is processed
for remote_region in other_regions
for remote_resource (domain/account/user) in remote_region.find_remote_resource_list( )
resource = current_region.find_same_resource( )
if resource != null && resource.status == remote_resource.status
continue
resource = current_region.find_the_latest_same_resource_including_removed( )
if resource != null && resource.removed > remote_resource.created
continue // this difference will be resolved in the remote_region
resource = current_region.create_same_resource( set resource.created as remote_resource.created )
if resource.status == remote_resource.status
continue
if resource.modified > remote_resource.modified
continue // this difference will be resolved in the remote_region
set resource.status as remote_resource.status
for resource in current_region.not_compared_resource_list
// resource can be either of below 2 cases
// 1. All remote regions have failed to create this resource : do nothing because this resource will be synced up in remote regions
// 2. Only current region has failed to remove this resource : just delete this resource
deleted = false
for remote_region in other_regions
remote_event_list = remote_region.find_event_list (
type = "<RESOURCE>.DELETE"
keyword = "Successfully completed deleting <resource>"
startdate = resource.created)
if the event of resource is in the event list
deleted = true
if deleted == true
current_region.delete (resource)
https://github.com/alexoughsg/Albatross/tree/albatross
https://github.com/alexoughsg/Albatross/pull/1/files