3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
22 """Handler for data copy operation.
24 Generic datastore admin console transfers control to ConfirmCopyHandler
25 after selection of entities. The ConfirmCopyHandler confirms with user
26 his choice, enters target application id and transfers control to
27 DoCopyHandler. DoCopyHandler starts copying mappers and displays confirmation
30 This module also contains actual mapper code for copying data over.
37 from google
.appengine
.api
import capabilities
38 from google
.appengine
.api
import datastore
39 from google
.appengine
.datastore
import datastore_rpc
40 from google
.appengine
.ext
import blobstore
41 from google
.appengine
.ext
import webapp
42 from google
.appengine
.ext
.datastore_admin
import remote_api_put_stub
43 from google
.appengine
.ext
.datastore_admin
import utils
44 from google
.appengine
.ext
.mapreduce
import context
45 from google
.appengine
.ext
.mapreduce
import operation
51 class ConfirmCopyHandler(webapp
.RequestHandler
):
52 """Handler to deal with requests from the admin console to copy data."""
54 SUFFIX
= 'confirm_copy'
57 def Render(cls
, handler
):
58 """Rendering method that can be called by main.py.
61 handler: the webapp.RequestHandler invoking the method
63 namespace
= handler
.request
.get('namespace')
64 kinds
= handler
.request
.get_all('kind')
65 sizes_known
, size_total
, remainder
= utils
.ParseKindsAndSizes(kinds
)
67 (namespace_str
, kind_str
) = utils
.GetPrintableStrs(namespace
, kinds
)
68 notreadonly_warning
= capabilities
.CapabilitySet(
69 'datastore_v3', capabilities
=['write']).is_enabled()
70 blob_warning
= bool(blobstore
.BlobInfo
.all().fetch(1))
71 datastore_type
= datastore
._GetConnection
().get_datastore_type()
72 high_replication_warning
= (
73 datastore_type
== datastore_rpc
.Connection
.HIGH_REPLICATION_DATASTORE
)
76 'form_target': DoCopyHandler
.SUFFIX
,
78 'remainder': remainder
,
79 'sizes_known': sizes_known
,
80 'size_total': size_total
,
81 'app_id': handler
.request
.get('app_id'),
82 'cancel_url': handler
.request
.get('cancel_url'),
84 'namespace_str': namespace_str
,
85 'xsrf_token': utils
.CreateXsrfToken(XSRF_ACTION
),
86 'notreadonly_warning': notreadonly_warning
,
87 'blob_warning': blob_warning
,
88 'high_replication_warning': high_replication_warning
,
90 utils
.RenderToResponse(handler
, 'confirm_copy.html', template_params
)
95 class DoCopyHandler(webapp
.RequestHandler
):
96 """Handler to deal with requests from the admin console to copy data."""
100 COPY_HANDLER
= ('google.appengine.ext.datastore_admin.copy_handler.'
101 'RemoteCopyEntity.map')
102 INPUT_READER
= ('google.appengine.ext.mapreduce.input_readers.'
103 'ConsistentKeyReader')
104 MAPREDUCE_DETAIL
= utils
.config
.MAPREDUCE_PATH
+ '/detail?mapreduce_id='
107 """Handler for get requests to datastore_admin/copy.do.
109 Status of executed jobs is displayed.
111 jobs
= self
.request
.get_all('job')
112 error
= self
.request
.get('error', '')
113 xsrf_error
= self
.request
.get('xsrf_error', '')
117 'mapreduce_detail': self
.MAPREDUCE_DETAIL
,
119 'xsrf_error': xsrf_error
,
120 'datastore_admin_home': utils
.config
.BASE_PATH
,
122 utils
.RenderToResponse(self
, 'do_copy.html', template_params
)
125 """Handler for post requests to datastore_admin/copy.do.
127 Jobs are executed and user is redirected to the get handler.
129 namespace
= self
.request
.get('namespace')
130 kinds
= self
.request
.get_all('kind')
131 (namespace_str
, kinds_str
) = utils
.GetPrintableStrs(namespace
, kinds
)
132 token
= self
.request
.get('xsrf_token')
133 remote_url
= self
.request
.get('remote_url')
134 extra_header
= self
.request
.get('extra_header')
138 parameters
= [('error', 'Unspecified remote URL.')]
139 elif not utils
.ValidateXsrfToken(token
, XSRF_ACTION
):
140 parameters
= [('xsrf_error', '1')]
146 extra_headers
= dict([extra_header
.split(':', 1)])
149 target_app
= remote_api_put_stub
.get_remote_appid(remote_url
,
151 op
= utils
.StartOperation(
152 'Copying %s%s to %s' % (kinds_str
, namespace_str
, target_app
))
153 name_template
= 'Copy all %(kind)s objects%(namespace)s'
155 'target_app': target_app
,
156 'remote_url': remote_url
,
157 'extra_header': extra_header
,
159 jobs
= utils
.RunMapForKinds(
172 logging
.exception('Handling exception.')
173 error
= self
._HandleException
(e
)
175 parameters
= [('job', job
) for job
in jobs
]
177 parameters
.append(('error', error
))
179 query
= urllib
.urlencode(parameters
)
180 self
.redirect('%s/%s?%s' % (utils
.config
.BASE_PATH
, self
.SUFFIX
, query
))
182 def _HandleException(self
, e
):
183 """Make exception handling overrideable by tests.
185 In normal cases, return only the error string; do not fail to render the
192 def KindPathFromKey(key
):
193 """Return kinds path as '/'-delimited string for a particular key."""
200 is_kind
= not is_kind
201 kind_path
= '/'.join(kinds
)
205 def get_mapper_params():
206 """Return current mapreduce mapper params. Easily stubbed out for testing."""
207 return context
.get().mapreduce_spec
.mapper
.params
210 class CopyEntity(object):
211 """A class which contains a map handler to copy entities."""
214 """Copy data map handler.
217 key: Datastore entity key or entity itself to copy.
220 A db operation to store the entity in the target app.
221 An operation which updates max used ID if necessary.
222 A counter operation incrementing the count for the entity kind.
225 mapper_params
= get_mapper_params()
226 target_app
= mapper_params
['target_app']
228 if isinstance(key
, datastore
.Entity
):
233 entity
= datastore
.Get(key
)
234 entity_proto
= entity
._ToPb
()
235 utils
.FixKeys(entity_proto
, target_app
)
236 target_entity
= datastore
.Entity
._FromPb
(entity_proto
)
238 yield operation
.db
.Put(target_entity
)
239 yield utils
.ReserveKey(key
, target_app
)
240 yield operation
.counters
.Increment(KindPathFromKey(key
))
243 class RemoteCopyEntity(CopyEntity
):
244 """A class which contains a map handler to copy entities remotely.
246 The class manages the connection.
250 super(RemoteCopyEntity
, self
).__init
__()
251 self
.remote_api_stub_initialized
= False
253 def setup_stub(self
):
254 """Set up the remote API stub."""
255 if self
.remote_api_stub_initialized
:
257 params
= get_mapper_params()
258 if 'extra_header' in params
and params
['extra_header']:
260 extra_headers
= dict([params
['extra_header'].split(':', 1)])
264 remote_api_put_stub
.configure_remote_put(params
['remote_url'],
265 params
['target_app'],
268 self
.remote_api_stub_initialized
= True
271 """Copy data map handler.
274 key: Datastore entity key to copy.
277 A db operation to store the entity in the target app.
278 An operation which updates max used ID if necessary.
279 A counter operation incrementing the count for the entity kind.
281 if not self
.remote_api_stub_initialized
:
284 for op
in CopyEntity
.map(self
, key
):
288 def handlers_list(base_path
):
290 (r
'%s/%s' % (base_path
, ConfirmCopyHandler
.SUFFIX
), ConfirmCopyHandler
),
291 (r
'%s/%s' % (base_path
, DoCopyHandler
.SUFFIX
), DoCopyHandler
),