Implement multi-tenant api publishing

merge-requests/900/head
Vigneshan Seshamany 2 years ago
parent 0290758c25
commit ebc5f5de31

@ -149,8 +149,8 @@
org.wso2.carbon.apimgt.webapp.publisher.*
</Export-Package>
<Import-Package>
com.google.gson;version="2.3",
com.google.gson.reflect;version="2.3",
com.google.gson;version="[2.3,2.8.6)",
com.google.gson.reflect;version="[2.3,2.8.6)",
io.swagger.annotations,
javax.servlet;version="2.6",
javax.xml,
@ -169,6 +169,7 @@
org.wso2.carbon.apimgt.api,
org.wso2.carbon.apimgt.api.model,
org.wso2.carbon.apimgt.impl,
org.wso2.carbon.apimgt.impl.utils,
org.wso2.carbon.apimgt.webapp.publisher,
org.wso2.carbon.apimgt.webapp.publisher.config,
org.wso2.carbon.apimgt.webapp.publisher.dto,

@ -18,6 +18,9 @@
*/
package org.wso2.carbon.apimgt.webapp.publisher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
import org.wso2.carbon.apimgt.webapp.publisher.dto.ApiScope;
import org.wso2.carbon.apimgt.webapp.publisher.dto.ApiUriTemplate;
import org.wso2.carbon.apimgt.api.APIManagementException;
@ -36,10 +39,15 @@ import org.wso2.carbon.apimgt.impl.APIManagerFactory;
import org.wso2.carbon.apimgt.webapp.publisher.config.WebappPublisherConfig;
import org.wso2.carbon.apimgt.webapp.publisher.exception.APIManagerPublisherException;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.user.core.tenant.Tenant;
import org.wso2.carbon.user.core.tenant.TenantSearchResult;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -55,146 +63,190 @@ public class APIPublisherServiceImpl implements APIPublisherService {
private static final String CREATED_STATUS = "CREATED";
private static final String PUBLISH_ACTION = "Publish";
public static final APIManagerFactory API_MANAGER_FACTORY = APIManagerFactory.getInstance();
private static final Log log = LogFactory.getLog(APIPublisherServiceImpl.class);
@Override
public void publishAPI(APIConfig apiConfig) throws APIManagerPublisherException {
String tenantDomain = MultitenantUtils.getTenantDomain(apiConfig.getOwner());
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(apiConfig.getOwner());
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
WebappPublisherConfig config = WebappPublisherConfig.getInstance();
List<String> tenants = new ArrayList<>(Collections.singletonList(APIConstants.SUPER_TENANT_DOMAIN));
tenants.addAll(config.getTenants().getTenant());
RealmService realmService = (RealmService) PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getOSGiService(RealmService.class, null);
try {
APIProvider apiProvider = API_MANAGER_FACTORY.getAPIProvider(apiConfig.getOwner());
APIIdentifier apiIdentifier = new APIIdentifier(apiConfig.getOwner(), apiConfig.getName(), apiConfig.getVersion());
if (!apiProvider.isAPIAvailable(apiIdentifier)) {
// add new scopes as shared scopes
Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain);
for (ApiScope apiScope : apiConfig.getScopes()) {
if (!allSharedScopeKeys.contains(apiScope.getKey())) {
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
}
}
API api = getAPI(apiConfig, true);
API createdAPI = apiProvider.addAPI(api);
if (CREATED_STATUS.equals(createdAPI.getStatus())) {
apiProvider.changeLifeCycleStatus(tenantDomain, createdAPI.getUuid(), PUBLISH_ACTION, null);
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(createdAPI.getUuid());
apiRevision.setDescription("Initial Revision");
String apiRevisionId = apiProvider.addAPIRevision(apiRevision, tenantDomain);
APIRevisionDeployment apiRevisionDeployment = new APIRevisionDeployment();
apiRevisionDeployment.setDeployment(API_PUBLISH_ENVIRONMENT);
apiRevisionDeployment.setVhost(System.getProperty("iot.gateway.host"));
apiRevisionDeployment.setDisplayOnDevportal(true);
List<APIRevisionDeployment> apiRevisionDeploymentList = new ArrayList<>();
apiRevisionDeploymentList.add(apiRevisionDeployment);
apiProvider.deployAPIRevision(createdAPI.getUuid(), apiRevisionId, apiRevisionDeploymentList);
boolean tenantFound = false;
boolean tenantsLoaded = false;
TenantSearchResult tenantSearchResult = null;
for (String tenantDomain : tenants) {
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
if (!tenantsLoaded) {
tenantSearchResult = realmService.getTenantManager()
.listTenants(Integer.MAX_VALUE, 0, "asc", "UM_ID", null);
tenantsLoaded = true;
}
} else {
if (WebappPublisherConfig.getInstance().isEnabledUpdateApi()) {
// With 4.x to 5.x upgrade
// - there cannot be same local scope assigned in 2 different APIs
// - local scopes will be deprecated in the future, so need to move all scopes as shared scopes
// if an api scope is not available as shared scope, but already assigned as local scope -> that means, the scopes available for this API has not moved as shared scopes
// in order to do that :
// 1. update the same API removing scopes from URI templates
// 2. add scopes as shared scopes
// 3. update the API again adding scopes for the URI Templates
// if an api scope is not available as shared scope, and not assigned as local scope -> that means, there are new scopes
// 1. add new scopes as shared scopes
// 2. update the API adding scopes for the URI Templates
Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain);
Set<ApiScope> scopesToMoveAsSharedScopes = new HashSet<>();
for (ApiScope apiScope : apiConfig.getScopes()) {
// if the scope is not available as shared scope and it is assigned to an API as a local scope
// need remove the local scope and add as a shared scope
if (!allSharedScopeKeys.contains(apiScope.getKey())) {
if (apiProvider.isScopeKeyAssignedLocally(apiIdentifier, apiScope.getKey(), tenantId)) {
// collect scope to move as shared scopes
scopesToMoveAsSharedScopes.add(apiScope);
} else {
// if new scope add as shared scope
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
}
if (tenantDomain.equals(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)) {
tenantFound = true;
realmService.getTenantUserRealm(MultitenantConstants.SUPER_TENANT_ID)
.getRealmConfiguration().getAdminUserName();
} else {
List<Tenant> allTenants = tenantSearchResult.getTenantList();
for (Tenant tenant : allTenants) {
if (tenant.getDomain().equals(tenantDomain)) {
tenantFound = true;
tenant.getAdminName();
break;
} else {
tenantFound = false;
}
}
}
// Get existing API
API existingAPI = apiProvider.getAPI(apiIdentifier);
if (scopesToMoveAsSharedScopes.size() > 0) {
// update API to remove local scopes
API api = getAPI(apiConfig, false);
api.setStatus(existingAPI.getStatus());
apiProvider.updateAPI(api);
for (ApiScope apiScope : scopesToMoveAsSharedScopes) {
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
if (tenantFound) {
PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(apiConfig.getOwner());
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
try {
apiConfig.setOwner(APIUtil.getTenantAdminUserName(tenantDomain));
apiConfig.setTenantDomain(tenantDomain);
APIProvider apiProvider = API_MANAGER_FACTORY.getAPIProvider(apiConfig.getOwner());
APIIdentifier apiIdentifier = new APIIdentifier(APIUtil.replaceEmailDomain(apiConfig.getOwner()),
apiConfig.getName(), apiConfig.getVersion());
if (!apiProvider.isAPIAvailable(apiIdentifier)) {
// add new scopes as shared scopes
Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain);
for (ApiScope apiScope : apiConfig.getScopes()) {
if (!allSharedScopeKeys.contains(apiScope.getKey())) {
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
}
}
API api = getAPI(apiConfig, true);
api.setId(apiIdentifier);
API createdAPI = apiProvider.addAPI(api);
if (CREATED_STATUS.equals(createdAPI.getStatus())) {
apiProvider.changeLifeCycleStatus(tenantDomain, createdAPI.getUuid(), PUBLISH_ACTION, null);
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(createdAPI.getUuid());
apiRevision.setDescription("Initial Revision");
String apiRevisionId = apiProvider.addAPIRevision(apiRevision, tenantDomain);
APIRevisionDeployment apiRevisionDeployment = new APIRevisionDeployment();
apiRevisionDeployment.setDeployment(API_PUBLISH_ENVIRONMENT);
apiRevisionDeployment.setVhost(System.getProperty("iot.gateway.host"));
apiRevisionDeployment.setDisplayOnDevportal(true);
List<APIRevisionDeployment> apiRevisionDeploymentList = new ArrayList<>();
apiRevisionDeploymentList.add(apiRevisionDeployment);
apiProvider.deployAPIRevision(createdAPI.getUuid(), apiRevisionId, apiRevisionDeploymentList);
}
} else {
if (WebappPublisherConfig.getInstance().isEnabledUpdateApi()) {
// With 4.x to 5.x upgrade
// - there cannot be same local scope assigned in 2 different APIs
// - local scopes will be deprecated in the future, so need to move all scopes as shared scopes
// if an api scope is not available as shared scope, but already assigned as local scope -> that means, the scopes available for this API has not moved as shared scopes
// in order to do that :
// 1. update the same API removing scopes from URI templates
// 2. add scopes as shared scopes
// 3. update the API again adding scopes for the URI Templates
// if an api scope is not available as shared scope, and not assigned as local scope -> that means, there are new scopes
// 1. add new scopes as shared scopes
// 2. update the API adding scopes for the URI Templates
Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain);
Set<ApiScope> scopesToMoveAsSharedScopes = new HashSet<>();
for (ApiScope apiScope : apiConfig.getScopes()) {
// if the scope is not available as shared scope and it is assigned to an API as a local scope
// need remove the local scope and add as a shared scope
if (!allSharedScopeKeys.contains(apiScope.getKey())) {
if (apiProvider.isScopeKeyAssignedLocally(apiIdentifier, apiScope.getKey(), tenantId)) {
// collect scope to move as shared scopes
scopesToMoveAsSharedScopes.add(apiScope);
} else {
// if new scope add as shared scope
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
}
}
}
// Get existing API
API existingAPI = apiProvider.getAPI(apiIdentifier);
if (scopesToMoveAsSharedScopes.size() > 0) {
// update API to remove local scopes
API api = getAPI(apiConfig, false);
api.setStatus(existingAPI.getStatus());
apiProvider.updateAPI(api);
for (ApiScope apiScope : scopesToMoveAsSharedScopes) {
Scope scope = new Scope();
scope.setName(apiScope.getName());
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey());
scope.setRoles(apiScope.getRoles());
apiProvider.addSharedScope(scope, tenantDomain);
}
}
existingAPI = apiProvider.getAPI(apiIdentifier);
API api = getAPI(apiConfig, true);
api.setStatus(existingAPI.getStatus());
apiProvider.updateAPI(api);
// Assumption: Assume the latest revision is the published one
String latestRevisionUUID = apiProvider.getLatestRevisionUUID(existingAPI.getUuid());
List<APIRevisionDeployment> latestRevisionDeploymentList =
apiProvider.getAPIRevisionDeploymentList(latestRevisionUUID);
List<APIRevision> apiRevisionList = apiProvider.getAPIRevisions(existingAPI.getUuid());
if (apiRevisionList.size() >= 5) {
String earliestRevisionUUID = apiProvider.getEarliestRevisionUUID(existingAPI.getUuid());
List<APIRevisionDeployment> earliestRevisionDeploymentList =
apiProvider.getAPIRevisionDeploymentList(earliestRevisionUUID);
apiProvider.undeployAPIRevisionDeployment(existingAPI.getUuid(), earliestRevisionUUID, earliestRevisionDeploymentList);
apiProvider.deleteAPIRevision(existingAPI.getUuid(), earliestRevisionUUID, tenantDomain);
}
// create new revision
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(existingAPI.getUuid());
apiRevision.setDescription("Updated Revision");
String apiRevisionId = apiProvider.addAPIRevision(apiRevision, tenantDomain);
apiProvider.deployAPIRevision(existingAPI.getUuid(), apiRevisionId, latestRevisionDeploymentList);
if (CREATED_STATUS.equals(existingAPI.getStatus())) {
apiProvider.changeLifeCycleStatus(tenantDomain, existingAPI.getUuid(), PUBLISH_ACTION, null);
}
}
}
}
existingAPI = apiProvider.getAPI(apiIdentifier);
API api = getAPI(apiConfig, true);
api.setStatus(existingAPI.getStatus());
apiProvider.updateAPI(api);
// Assumption: Assume the latest revision is the published one
String latestRevisionUUID = apiProvider.getLatestRevisionUUID(existingAPI.getUuid());
List<APIRevisionDeployment> latestRevisionDeploymentList =
apiProvider.getAPIRevisionDeploymentList(latestRevisionUUID);
List<APIRevision> apiRevisionList = apiProvider.getAPIRevisions(existingAPI.getUuid());
if (apiRevisionList.size() >= 5) {
String earliestRevisionUUID = apiProvider.getEarliestRevisionUUID(existingAPI.getUuid());
List<APIRevisionDeployment> earliestRevisionDeploymentList =
apiProvider.getAPIRevisionDeploymentList(earliestRevisionUUID);
apiProvider.undeployAPIRevisionDeployment(existingAPI.getUuid(), earliestRevisionUUID, earliestRevisionDeploymentList);
apiProvider.deleteAPIRevision(existingAPI.getUuid(), earliestRevisionUUID, tenantDomain);
}
// create new revision
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(existingAPI.getUuid());
apiRevision.setDescription("Updated Revision");
String apiRevisionId = apiProvider.addAPIRevision(apiRevision, tenantDomain);
apiProvider.deployAPIRevision(existingAPI.getUuid(), apiRevisionId, latestRevisionDeploymentList);
if (CREATED_STATUS.equals(existingAPI.getStatus())) {
apiProvider.changeLifeCycleStatus(tenantDomain, existingAPI.getUuid(), PUBLISH_ACTION, null);
} catch (FaultGatewaysException | APIManagementException e) {
String msg = "Error occurred while publishing api";
log.error(msg, e);
throw new APIManagerPublisherException(e);
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
}
}
} catch (FaultGatewaysException | APIManagementException e) {
} catch (UserStoreException e) {
String msg = "Error occurred while retrieving admin user from tenant user realm";
log.error(msg, e);
throw new APIManagerPublisherException(e);
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
}
@ -209,7 +261,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
api.setStatus(CREATED_STATUS);
api.setWsdlUrl(null);
api.setResponseCache("Disabled");
api.setContextTemplate(context + "/{version}" );
api.setContextTemplate(context + "/{version}");
api.setSwaggerDefinition(APIPublisherUtil.getSwaggerDefinition(config));
api.setType("HTTP");
@ -262,7 +314,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
api.setVisibility(APIConstants.API_PRIVATE_VISIBILITY);
}
String endpointConfig = "{ \"endpoint_type\": \"http\", \"sandbox_endpoints\": { \"url\": \" " +
config.getEndpoint() + "\" }, \"production_endpoints\": { \"url\": \" "+ config.getEndpoint()+"\" } }";
config.getEndpoint() + "\" }, \"production_endpoints\": { \"url\": \" " + config.getEndpoint() + "\" } }";
api.setEndpointConfig(endpointConfig);
List<String> accessControlAllowOrigins = new ArrayList<>();

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.wso2.carbon.apimgt.webapp.publisher.config;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Tenants", propOrder = {
"tenant"
})
public class Tenants {
@XmlElement(name = "Tenant")
protected List<String> tenant = new ArrayList<>();;
/**
* Gets the value of the profile property.
*
*/
public List<String> getTenant() {
return this.tenant;
}
}

@ -43,6 +43,7 @@ public class WebappPublisherConfig {
private boolean isEnabledUpdateApi;
private Profiles profiles;
private static boolean isInitialized = false;
private Tenants tenants;
private static WebappPublisherConfig config;
@ -100,6 +101,15 @@ public class WebappPublisherConfig {
this.profiles = profiles;
}
@XmlElement(name = "Tenants", required = true)
public Tenants getTenants() {
return tenants;
}
public void setTenants(Tenants tenants) {
this.tenants = tenants;
}
public synchronized static void init() throws WebappPublisherConfigurationFailedException {
if (isInitialized) {
return;

@ -36,4 +36,9 @@
<Profiles>
<Profile>default</Profile>
</Profiles>
</WebappPublisherConfigs>
<!-- Apart from the super tenant, APIs will be published to the following tenants -->
<Tenants>
<!-- <Tenant>example.com</Tenant> -->
</Tenants>
</WebappPublisherConfigs>

@ -53,4 +53,13 @@
{% endfor %}
{% endif %}
</Profiles>
</WebappPublisherConfigs>
<!-- Apart from the super tenant, APIs will be published to the following tenants -->
<Tenants>
{% if webapp_publisher_configs.tenants is defined %}
{%- for tenant in webapp_publisher_configs.tenants -%}
<Tenant>{{tenant}}</Tenant>
{% endfor %}
{% endif %}
</Tenants>
</WebappPublisherConfigs>

Loading…
Cancel
Save