class MsoKeystoneUtils extends MsoTenantUtils {

    public String createTenant(String tenantName, String cloudSiteId, Map<String, String> metadata, boolean backout)
            throws MsoException {
        // Obtain the cloud site information where we will create the tenant
        Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
        if (!cloudSiteOpt.isPresent()) {
            logger.error("{} MSOCloudSite {} not found {} ", MessageEnum.RA_CREATE_TENANT_ERR, cloudSiteId,
                    ErrorCode.DataError.getValue());
            throw new MsoCloudSiteNotFound(cloudSiteId);
        }
        Keystone keystoneAdminClient = getKeystoneAdminClient(cloudSiteOpt.get());
        Tenant tenant = null;
        try {
            // Check if the tenant already exists
            tenant = findTenantByName(keystoneAdminClient, tenantName);

            if (tenant != null) {
                // Tenant already exists. Throw an exception
                logger.error("{} Tenant name {} already exists on Cloud site id {}, {}",
                        MessageEnum.RA_TENANT_ALREADY_EXIST, tenantName, cloudSiteId, ErrorCode.DataError.getValue());
                throw new MsoTenantAlreadyExists(tenantName, cloudSiteId);
            }

            // Does not exist, create a new one
            tenant = new Tenant();
            tenant.setName(tenantName);
            tenant.setDescription("SDN Tenant (via MSO)");
            tenant.setEnabled(true);

            OpenStackRequest<Tenant> request = keystoneAdminClient.tenants().create(tenant);
            tenant = executeAndRecordOpenstackRequest(request);
        } catch (OpenStackBaseException e) {
            // Convert Keystone OpenStackResponseException to MsoOpenstackException
            throw keystoneErrorToMsoException(e, "CreateTenant");
        } catch (RuntimeException e) {
            // Catch-all
            throw runtimeExceptionToMsoException(e, "CreateTenant");
        }

        // Add MSO User to the tenant as a member and
        // apply tenant metadata if supported by the cloud site
        try {
            CloudIdentity cloudIdentity = cloudSiteOpt.get().getIdentityService();

            User msoUser = findUserByNameOrId(keystoneAdminClient, cloudIdentity.getMsoId());
            Role memberRole = findRoleByNameOrId(keystoneAdminClient, cloudIdentity.getMemberRole());

            if (msoUser != null && memberRole != null) {
                OpenStackRequest<Void> request =
                        keystoneAdminClient.tenants().addUser(tenant.getId(), msoUser.getId(), memberRole.getId());
                executeAndRecordOpenstackRequest(request);
            }

            if (cloudIdentity.getTenantMetadata() && metadata != null && !metadata.isEmpty()) {
                Metadata tenantMetadata = new Metadata();
                tenantMetadata.setMetadata(metadata);

                OpenStackRequest<Metadata> metaRequest =
                        keystoneAdminClient.tenants().createOrUpdateMetadata(tenant.getId(), tenantMetadata);
                executeAndRecordOpenstackRequest(metaRequest);
            }
        } catch (Exception e) {
            // Failed to attach MSO User to the new tenant. Can't operate without access,
            // so roll back the tenant.
            if (!backout) {
                logger.warn("{} Create Tenant errored, Tenant deletion suppressed {} ",
                        MessageEnum.RA_CREATE_TENANT_ERR, ErrorCode.DataError.getValue());
            } else {
                try {
                    OpenStackRequest<Void> request = keystoneAdminClient.tenants().delete(tenant.getId());
                    executeAndRecordOpenstackRequest(request);
                } catch (Exception e2) {
                    // Just log this one. We will report the original exception.
                    logger.error("{} Nested exception rolling back tenant {} ", MessageEnum.RA_CREATE_TENANT_ERR,
                            ErrorCode.DataError.getValue(), e2);
                }
            }


            // Propagate the original exception on user/role/tenant mapping
            if (e instanceof OpenStackBaseException) {
                // Convert Keystone Exception to MsoOpenstackException
                throw keystoneErrorToMsoException((OpenStackBaseException) e, "CreateTenantUser");
            } else {
                MsoAdapterException me = new MsoAdapterException(e.getMessage(), e);
                me.addContext("CreateTenantUser");
                throw me;
            }
        }
        return tenant.getId();
    }

}