-
Notifications
You must be signed in to change notification settings - Fork 64
multitenancy
Not applicable to this runtime.
The Namespaces API is used with used with other App Engine services.
The Namespaces API allows you to easily enable multitenancy in your
application, simply by selecting a namespace string for each tenant in
web.xml
in appengine_config.py
using the NamespaceManager package.
with other App Engine tools and APIs.
using the namespace_manager package.
You can get, set, and validate namespaces using the NamespaceManager package.
with other App Engine tools and APIs.
using the namespace_manager package. The namespace manager allows you to set a
current namespace for namespace-enabled
APIs. You set a current
namespace up-front in web.xml
in appengine_config.py and the Datastore and memcache automatically use that
namespace.
Most App Engine developers will use their {{g_suite_name}} (formerly G Suite) domain as the current namespace. {{g_suite_name}} lets you deploy your app to any domain that you own, so you can easily use this mechanism to configure different namespaces for different domains. Then, you can use those separate namespaces to segregate data across the domains. For more information, see Mapping Custom Domains.
The following code sample shows you how to set the current namespace to the {{g_suite_name}} domain that was used to map the URL. Notably, this string will be the same for all URLs mapped via the same {{g_suite_name}} domain.
You can set namespaces in Java using the servlet Filter interface before invoking servlet methods. The following code sample demonstrates how to use your {{g_suite_name}} domain as the current namespace:
View NamespaceFilter.java on GitHub (region:
nsfilter)
The namespace filter must be configured in the web.xml file. Note that, if
there are multiple filter entries, the first namespace to be set is the one that
will be used.
The following code sample demonstrates how to configure the namespace filter in
web.xml:
View web.xml on GitHub (region:
nsfilter)
For more general information on the web.xml file and mapping URL paths to
servlets, refer to The Deployment Descriptor:
web.xml.
You can also set a new namespace for a temporary operation, resetting the
original namespace once the operation is complete, using the try/finally
pattern shown below:
View MultitenancyServlet.java on GitHub (region:
temp_namespace)
To set a namespace in Python, use the App Engine configuration system
appengine_config.py in your application's root directory. The following simple
example demonstrates how to use your {{g_suite_name}} domain as the current
namespace:
If you do not specify a value for namespace, the namespace is set to an empty
string. The namespace string is arbitrary, but also limited to a maximum of
100 alphanumeric characters, periods, underscores, and hyphens. More explicitly,
namespace strings must match the regular expression [0-9A-Za-z._-]{0,100}.
By convention, all namespaces starting with "_" (underscore) are reserved for
system use. This system namespace rule is not enforced, but you could easily
encounter undefined negative consequences if you do not follow it.
One of the risks commonly associated with multitenant apps is the danger that data will leak across namespaces. Unintended data leaks can arise from many sources, including:
- Using namespaces with App Engine APIs that do not yet support namespaces. For example, Blobstore does not support namespaces. If you use Namespaces with Blobstore, you need to avoid using Blobstore queries for end user requests, or Blobstore keys from untrusted sources.
- Using an external storage medium (instead of memcache and Datastore), via
URL Fetchor some other mechanism, without providing a compartmentalization scheme for namespaces. - Setting a namespace based on a user's email domain. In most cases, you don't want all email addresses of a domain to access a namespace. Using the email domain also prevents your application from using a namespace until the user is logged in.
The following sections describe how to deploy namespaces with other App Engine tools and APIs.
Some applications need to create namespaces on a per-user basis. If you want to
compartmentalize data at the user level for logged-in users, consider using
User.getUserId()
User.user_id()
, which returns a unique, permanent ID for the user. The following code sample
demonstrates how to use the Users API for this purpose:
View MultitenancyServlet.java on GitHub (region:
per_user_namespace)
Typically, apps that create namespaces on a per-user basis also provide specific landing pages to different users. In these cases, the application needs to provide a URL scheme dictating which landing page to display to a user.
By default, the Datastore uses the current namespace setting in the namespace
manager for Datastore requests. The API applies this current namespace to
Key
or
Query
Key
or
Query
objects when they are created. Therefore, you need to be careful if an
application stores Key or Query objects in serialized forms, since the
namespace is preserved in those serializations.
If you are using deserialized Key and Query objects, make sure that they
behave as intended. Most simple applications that use Datastore
(put/query/get) without using other storage mechanisms will work as
expected by setting the current namespace before calling any Datastore API.
Note: An application that reads Keys, or other namespace-aware objects, from untrusted sources (like the web browser client) introduces security vulnerabilities. Applications that rely on keys from untrusted sources must incorporate a security layer verifying that the current user is authorized to access the requested namespace.
Query and Key objects demonstrate the following, unique behaviors with
regard to namespaces:
-
QueryandKeyobjects inherit the current namespace when constructed, unless you set an explicit namespace. - When an application creates a new
Keyfrom an ancestor, the newKeyinherits the namespace of the ancestor. - There is no API for Java to explicitly set the namespace of a
KeyorQuery.
The following code example shows the SomeRequest request handler for
incrementing the count for the current namespace and the arbitrarily named
-global- namespace in a Counter datastore entity.
View UpdateCountsServlet.java on GitHub (region:
datastore)
By default, memcache uses the current namespace from the namespace manager for memcache requests. In most cases, you do not need to explicitly set a namespace in the memcache, and doing so could introduce unexpected bugs.
However, there are some unique instances where it is appropriate to explicitly set a namespace in the memcache. For example, your application might have common data shared across all namespaces (such as a table containing country codes).
Warning: If you explicitly set a namespace in the memcache, it will ignore the current settings from the namespace manager.
The following code snippet demonstrates how to explicitly set the namespace in the memcache:
By default, the memcache API for Java queries the namespace manager for the
current namespace from MemcacheService. You can also explicitly state a
namespace when you construct the memcache using getMemcacheService(Namespace).
For most applications, you don't need to explicitly specify a namespace.
The following code sample demonstrates how to create a memcache that uses the current namespace in the namespace manager.
View MultitenancyServlet.java on GitHub (region:
ns_memcache)
This code sample explicitly specifies a namespace when creating a memcache service:
View MultitenancyServlet.java on GitHub (region:
specific_memcache)
Using the Python API for memcache, you can get the current namespace from the namespace manager or set it explicitly when you create the memcache service. The example below sets the namespace explicitly when you store a value in memcache:
By default, push queues use the current namespace as set in the namespace manager at the time the task was created. In most cases, you do not need to explicitly set a namespace in the task queue, and doing so could introduce unexpected bugs.
Warning: Tasks in pull queues do not provide any namespace functionality. If you use namespaces with pull queues, you need to ensure that namespaces are saved in the payload and restored as needed by the application.
Task names are shared across all namespaces. You cannot create two tasks of the same name, even if they use different namespaces. If you wish to use the same task name for many namespaces, you can simply append each namespace to the task name.
When a new task calls the task queue
add()
add()
method, the task queue copies the current namespace and (if applicable) the
{{g_suite_name}} domain from the namespace manager. When the task is executed,
the current namespace and {{g_suite_name}} namespace are restored.
If the current namespace is not set in the originating request (in other words,
if get() returns null), then the task queue sets the namespace to "" in
the executed tasks.
If the current namespace is not set in the originating request (in other words,
if get_namespace() returns ''), you can use set_namespace() to set the
current namespace for the task.
There are some unique instances where it is appropriate to explicitly set a namespace for a task that works across all namespaces. For example, you might create a task that aggregates usage statistics across all namespaces. You could then explicitly set the namespace of the task.
First, create a task queue handler that increments the count in a Counter
datastore entity:
View UpdateCountsServlet.java on GitHub (region:
tq_1)
and,
View UpdateCountsServlet.java on GitHub (region:
tq_2)
Then, create tasks with a servlet:
View SomeRequestServlet.java on GitHub (region:
tq_3)
The Blobstore is not segmented by namespace. To preserve a namespace in
Blobstore, you need to access Blobstore via a storage medium that is aware of
the namespace (currently only memcache, Datastore, and task queue). For example,
if a blob's Key is stored in a Datastore entity, you can access it with a
Datastore Key or Query that is aware of the namespace.
If the application is accessing Blobstore via keys stored in namespace-aware storage, the Blobstore itself does not need to be segmented by namespace. Applications must avoid blob leaks between namespaces by:
- Not using
com.google.appengine.api.blobstore.BlobInfoFactory
BlobInfo
BlobInfo.gql() for end-user requests. You can use BlobInfo queries for
administrative requests (such as generating reports about all the applications
blobs), but using it for end-user requests may result in data leaks because all
BlobInfo records are not compartmentalized by namespace.
- Not using Blobstore keys from untrusted sources.
In the Google Cloud console, you can set the namespace for Datastore queries.
If you don't want to use the default, select the namespace you want to use from the drop-down.
The bulk loader supports a --namespace=NAMESPACE flag that allows you to
specify the namespace to use. Each namespace is handled separately and, if you
want to access all namespaces, you will need to iterate through them.
A new instance of Index inherits the namespace of the SearchService used to
create it. Once you've created a reference to an index, its namespace cannot be
changed. There are two ways to set the namespace for a SearchService before
using it to create an index:
- By default, a new
SearchServicetakes the current namespace. You can set the current namespace before creating the service:
View MultitenancyServlet.java on GitHub (region:
searchns)
- You can specify a namespace in the
SearchServiceConfigwhen creating a service:
View MultitenancyServlet.java on GitHub (region:
searchns_2)
When you create a new instance of Index, it is assigned to the current
namespace by default:
# set the current namespace
namespace_manager.set_namespace("aSpace")
index = search.Index(name="myIndex")
# index namespace is now fixed to "aSpace"
You can also assign a namespace explicitly in the constructor:
index = search.Index(name="myIndex", namespace="aSpace")
Once you've created an index spec, its namespace cannot be changed:
# change the current namespace
namespace_manager.set_namespace("anotherSpace")
# the namespaceof 'index' is still "aSpace" because it was bound at create time
index.search('hello')