Merge branch 'master' into 'master'

Implement multi-tenant api publishing

See merge request entgra/carbon-device-mgt!900
feature/traccar-sync
Pahansith Gunathilake 2 years ago
commit cd6a4e3c0c

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

@ -18,6 +18,9 @@
*/ */
package org.wso2.carbon.apimgt.webapp.publisher; 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.ApiScope;
import org.wso2.carbon.apimgt.webapp.publisher.dto.ApiUriTemplate; import org.wso2.carbon.apimgt.webapp.publisher.dto.ApiUriTemplate;
import org.wso2.carbon.apimgt.api.APIManagementException; 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.config.WebappPublisherConfig;
import org.wso2.carbon.apimgt.webapp.publisher.exception.APIManagerPublisherException; import org.wso2.carbon.apimgt.webapp.publisher.exception.APIManagerPublisherException;
import org.wso2.carbon.context.PrivilegedCarbonContext; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -55,146 +63,190 @@ public class APIPublisherServiceImpl implements APIPublisherService {
private static final String CREATED_STATUS = "CREATED"; private static final String CREATED_STATUS = "CREATED";
private static final String PUBLISH_ACTION = "Publish"; private static final String PUBLISH_ACTION = "Publish";
public static final APIManagerFactory API_MANAGER_FACTORY = APIManagerFactory.getInstance(); public static final APIManagerFactory API_MANAGER_FACTORY = APIManagerFactory.getInstance();
private static final Log log = LogFactory.getLog(APIPublisherServiceImpl.class);
@Override @Override
public void publishAPI(APIConfig apiConfig) throws APIManagerPublisherException { public void publishAPI(APIConfig apiConfig) throws APIManagerPublisherException {
String tenantDomain = MultitenantUtils.getTenantDomain(apiConfig.getOwner()); WebappPublisherConfig config = WebappPublisherConfig.getInstance();
PrivilegedCarbonContext.startTenantFlow(); List<String> tenants = new ArrayList<>(Collections.singletonList(APIConstants.SUPER_TENANT_DOMAIN));
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); tenants.addAll(config.getTenants().getTenant());
PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(apiConfig.getOwner()); RealmService realmService = (RealmService) PrivilegedCarbonContext.getThreadLocalCarbonContext()
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); .getOSGiService(RealmService.class, null);
try { try {
APIProvider apiProvider = API_MANAGER_FACTORY.getAPIProvider(apiConfig.getOwner()); boolean tenantFound = false;
APIIdentifier apiIdentifier = new APIIdentifier(apiConfig.getOwner(), apiConfig.getName(), apiConfig.getVersion()); boolean tenantsLoaded = false;
TenantSearchResult tenantSearchResult = null;
if (!apiProvider.isAPIAvailable(apiIdentifier)) { for (String tenantDomain : tenants) {
PrivilegedCarbonContext.startTenantFlow();
// add new scopes as shared scopes PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain); if (!tenantsLoaded) {
for (ApiScope apiScope : apiConfig.getScopes()) { tenantSearchResult = realmService.getTenantManager()
if (!allSharedScopeKeys.contains(apiScope.getKey())) { .listTenants(Integer.MAX_VALUE, 0, "asc", "UM_ID", null);
Scope scope = new Scope(); tenantsLoaded = true;
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);
} }
} else { if (tenantDomain.equals(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)) {
if (WebappPublisherConfig.getInstance().isEnabledUpdateApi()) { tenantFound = true;
realmService.getTenantUserRealm(MultitenantConstants.SUPER_TENANT_ID)
// With 4.x to 5.x upgrade .getRealmConfiguration().getAdminUserName();
// - there cannot be same local scope assigned in 2 different APIs } else {
// - local scopes will be deprecated in the future, so need to move all scopes as shared scopes List<Tenant> allTenants = tenantSearchResult.getTenantList();
for (Tenant tenant : allTenants) {
// 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 if (tenant.getDomain().equals(tenantDomain)) {
// in order to do that : tenantFound = true;
// 1. update the same API removing scopes from URI templates tenant.getAdminName();
// 2. add scopes as shared scopes break;
// 3. update the API again adding scopes for the URI Templates } else {
tenantFound = false;
// 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 if (tenantFound) {
API existingAPI = apiProvider.getAPI(apiIdentifier); PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(apiConfig.getOwner());
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
if (scopesToMoveAsSharedScopes.size() > 0) {
// update API to remove local scopes try {
API api = getAPI(apiConfig, false); apiConfig.setOwner(APIUtil.getTenantAdminUserName(tenantDomain));
api.setStatus(existingAPI.getStatus()); apiConfig.setTenantDomain(tenantDomain);
apiProvider.updateAPI(api); APIProvider apiProvider = API_MANAGER_FACTORY.getAPIProvider(apiConfig.getOwner());
APIIdentifier apiIdentifier = new APIIdentifier(APIUtil.replaceEmailDomain(apiConfig.getOwner()),
for (ApiScope apiScope : scopesToMoveAsSharedScopes) { apiConfig.getName(), apiConfig.getVersion());
Scope scope = new Scope();
scope.setName(apiScope.getName()); if (!apiProvider.isAPIAvailable(apiIdentifier)) {
scope.setDescription(apiScope.getDescription());
scope.setKey(apiScope.getKey()); // add new scopes as shared scopes
scope.setRoles(apiScope.getRoles()); Set<String> allSharedScopeKeys = apiProvider.getAllSharedScopeKeys(tenantDomain);
apiProvider.addSharedScope(scope, 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);
}
}
} }
} } catch (FaultGatewaysException | APIManagementException e) {
String msg = "Error occurred while publishing api";
existingAPI = apiProvider.getAPI(apiIdentifier); log.error(msg, e);
API api = getAPI(apiConfig, true); throw new APIManagerPublisherException(e);
api.setStatus(existingAPI.getStatus()); } finally {
apiProvider.updateAPI(api); PrivilegedCarbonContext.endTenantFlow();
// 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 (UserStoreException e) {
String msg = "Error occurred while retrieving admin user from tenant user realm";
} catch (FaultGatewaysException | APIManagementException e) { log.error(msg, e);
throw new APIManagerPublisherException(e); throw new APIManagerPublisherException(e);
} finally {
PrivilegedCarbonContext.endTenantFlow();
} }
} }
@ -209,7 +261,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
api.setStatus(CREATED_STATUS); api.setStatus(CREATED_STATUS);
api.setWsdlUrl(null); api.setWsdlUrl(null);
api.setResponseCache("Disabled"); api.setResponseCache("Disabled");
api.setContextTemplate(context + "/{version}" ); api.setContextTemplate(context + "/{version}");
api.setSwaggerDefinition(APIPublisherUtil.getSwaggerDefinition(config)); api.setSwaggerDefinition(APIPublisherUtil.getSwaggerDefinition(config));
api.setType("HTTP"); api.setType("HTTP");
@ -262,7 +314,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
api.setVisibility(APIConstants.API_PRIVATE_VISIBILITY); api.setVisibility(APIConstants.API_PRIVATE_VISIBILITY);
} }
String endpointConfig = "{ \"endpoint_type\": \"http\", \"sandbox_endpoints\": { \"url\": \" " + 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); api.setEndpointConfig(endpointConfig);
List<String> accessControlAllowOrigins = new ArrayList<>(); 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 boolean isEnabledUpdateApi;
private Profiles profiles; private Profiles profiles;
private static boolean isInitialized = false; private static boolean isInitialized = false;
private Tenants tenants;
private static WebappPublisherConfig config; private static WebappPublisherConfig config;
@ -100,6 +101,15 @@ public class WebappPublisherConfig {
this.profiles = profiles; 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 { public synchronized static void init() throws WebappPublisherConfigurationFailedException {
if (isInitialized) { if (isInitialized) {
return; return;

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

@ -53,4 +53,13 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</Profiles> </Profiles>
<!-- 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> </WebappPublisherConfigs>
Loading…
Cancel
Save