...
https://issues.apache.org/jira/browse/CLOUDSTACK-4992
4.3 4
Currently, under the environment of cloudstack with multiple regions, each region has its own management server running with a separate database, which will cause data discrepancies when users create/update/delete domain/account/user data independently in each management server. So if we want to support multiple regions and provide one point of entry for a each customer, we need to duplicate domain/account/user information of that each 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.
There can be 2 different approaches.
...
All management servers allow manual changes and any change in any server will be propagated to the rest of servers.
Field | Type | Desc |
---|---|---|
name | char(255) | name of the region |
host | char(255) | API server |
root_url | char(255) | root url for API Interfaces |
user_name | char(255) | user name for API call authentication |
password | char(255) | password for API call authentication |
is_master | boolean | true if this region is the master |
Field | Type | Desc |
---|---|---|
routing_key | char(255) | routing key of an event message |
body | char(255) | body of an event message |
created_time | datetime | when this record is created |
processed_time | datetime | when this record was processed |
result | boolean | true if succeeded |
message | char(255) | error message |
'source' : uuid of a resource in this local region
'region_id' : id of a remote region in 'region' table
'created' : timestamp when this map is created
'modified' : timestamp when this map is last modified
'removed' : timestamp when this map is removed
A new class that extends EventBus class to register domain/account/user action event subscribers.
<bean id="eventNotificationBus" class="org.apache.cloudstack.mom.rabbitmq.MultiRegionEventBus">
<property name="name" value="eventNotificationBus" />
<property name="server" value="localhost" />
<property name="port" value="5672" />
<property name="username" value="guest" />
<property name="password" value="guest" />
<property name="exchange" value="cloudstack-events" />
</bean>
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));
......
New classes that catch action events of domain/account/user and request the remote regions to apply the same changes.
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);
New classes to send requests to remote regions using cloudstack API
'region.full.scan.interval' : time interval in milliseconds to run the full scan
get the list of local resources
for each remote region
get the list of remote resources
for each resource in the local list
if there is a remote resource that is linked to this local resource
compare the 'modified' datetimes of local & remote resources
if they are same, no work
if both attribute values are same, no work
otherwise,
if the remote is after the local, UPDATE local attribute values to those of the remote including 'modified' timestamp
if the local is after, no work (This will be sync'ed up in the remote region)
remove this local from the local list
remove this remote from the remote listotherwise, no work
for local resources that are still in the local list
find the latest removal event of the linked resource in the remote region
if an event is found in the remote region and the 'created' timestamp of the removal event is later than the 'created' timestamp of this local resource,
remove the local resource with remote event 'created' timestamp
remove this local from the local list
otherwise, no workfor remote resources that are still in the remote list
find if the local resource was removed
if it was removed and the 'removed' timestamp is later than the 'created' of the remote,
don't create this in the local region
otherwise,
create this resource in the local region with remote resource attribute values including 'created', 'modified'
remove this remote from the remote list
To provide details of resources that have been created/modified/deleted, the resource names along with those of parents are 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
Successfully completed updating Domain. Domain Id: 25, Domain Path:domain_path, Old Entity Name:old_name, New Entity Name:new_name
Successfully completed updating account, Account Name:account1, Domain Path:domain_path, Old Entity Name:old_name, New Entity Name:new_name
Successfully completed updating User. UserId: 34, User Name:user1, Account Name:account, Domain Path:domain_path, Old Entity Name:old_name, New Entity Name:new_name
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
...
https://github.com/alexoughsg/Albatross/tree/albatross
https://github.com/alexoughsg/Albatross/pull/1/files