Skip to content

Commit 1279c3b

Browse files
[CosmosDb] Add mongo collection copy command (#5506)
* Remove preview tag * Add mongo collection copy * Rename param * Add extra space * Add new version Co-authored-by: Nitesh Vijay <[email protected]>
1 parent b714fbe commit 1279c3b

File tree

87 files changed

+1538
-515
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+1538
-515
lines changed

src/cosmosdb-preview/HISTORY.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
33
Release History
44
===============
5+
0.21.0
6+
* Add support for mongo data transfer jobs.
7+
8+
++++++
59
0.20.0
610
* Add support for Continuous mode restore with user provided identity.
711

src/cosmosdb-preview/azext_cosmosdb_preview/_help.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,14 +567,29 @@
567567
Usage: --dest-sql-container database=XX container=XX'
568568
database: Database name of CosmosDB Sql.
569569
container: Container name of CosmosDB Sql.
570+
- name: --source-mongo
571+
short-summary: "Source mongo collection"
572+
long-summary: |
573+
Usage: --source-mongo database=XX collection=XX'
574+
database: Database name of CosmosDB Mongo.
575+
collection: Collection name of CosmosDB Mongo.
576+
- name: --dest-mongo
577+
short-summary: "Destination mongo collection"
578+
long-summary: |
579+
Usage: --dest-mongo database=XX collection=XX'
580+
database: Database name of CosmosDB Mongo.
581+
collection: Collection name of CosmosDB Mongo.
570582
571583
examples:
572584
- name: Copy sql container
573585
text: |-
574586
az cosmosdb dts copy -g "rg1" --job-name "j1" --account-name "db1" --source-sql-container database=db1 container=c1 --dest-sql-container database=db2 container=c2
575587
- name: Copy cassandra table
576588
text: |-
577-
az cosmosdb dts copy -g "rg1" --job-name "j1" --account-name "db1" --source-cassandra-table keyspace=k1 table=t1 --dest-cassandra-table keyspace=k1 table=t1
589+
az cosmosdb dts copy -g "rg1" --job-name "j1" --account-name "db1" --source-cassandra-table keyspace=k1 table=t1 --dest-cassandra-table keyspace=k2 table=t2
590+
- name: Copy mongo collection
591+
text: |-
592+
az cosmosdb dts copy -g "rg1" --job-name "j1" --account-name "db1" --source-mongo database=d1 collection=c1 --dest-mongo database=d2 collection=c2
578593
"""
579594

580595
helps['cosmosdb dts'] = """

src/cosmosdb-preview/azext_cosmosdb_preview/_params.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
CreateGremlinDatabaseRestoreResource,
2222
CreateTableRestoreResource,
2323
AddCassandraTableAction,
24+
AddMongoCollectionAction,
2425
AddSqlContainerAction,
2526
CreateTargetPhysicalPartitionThroughputInfoAction,
2627
CreateSourcePhysicalPartitionThroughputInfoAction,
@@ -314,8 +315,10 @@ def load_arguments(self, _):
314315
with self.argument_context('cosmosdb dts copy') as c:
315316
c.argument('job_name', job_name_type)
316317
c.argument('source_cassandra_table', nargs='+', action=AddCassandraTableAction, help='Source cassandra table')
318+
c.argument('source_mongo', nargs='+', action=AddMongoCollectionAction, help='Source mongo collection')
317319
c.argument('source_sql_container', nargs='+', action=AddSqlContainerAction, help='Source sql container')
318320
c.argument('dest_cassandra_table', nargs='+', action=AddCassandraTableAction, help='Destination cassandra table')
321+
c.argument('dest_mongo', nargs='+', action=AddMongoCollectionAction, help='Destination mongo collection')
319322
c.argument('dest_sql_container', nargs='+', action=AddSqlContainerAction, help='Destination sql container')
320323
c.argument('worker_count', type=int, help='Worker count')
321324

src/cosmosdb-preview/azext_cosmosdb_preview/actions.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
DatabaseRestoreResource,
1414
GremlinDatabaseRestoreResource,
1515
CosmosCassandraDataTransferDataSourceSink,
16+
CosmosMongoDataTransferDataSourceSink,
1617
CosmosSqlDataTransferDataSourceSink,
1718
PhysicalPartitionThroughputInfoResource,
1819
PhysicalPartitionId
@@ -138,6 +139,45 @@ def __call__(self, parser, namespace, values, option_string=None):
138139
namespace.cassandra_table = cassandra_table
139140

140141

142+
class AddMongoCollectionAction(argparse._AppendAction):
143+
def __call__(self, parser, namespace, values, option_string=None):
144+
if not values:
145+
# pylint: disable=line-too-long
146+
raise CLIError(f'usage error: {option_string} [KEY=VALUE ...]')
147+
148+
database_name = None
149+
collection_name = None
150+
151+
for (k, v) in (x.split('=', 1) for x in values):
152+
kl = k.lower()
153+
if kl == 'database':
154+
database_name = v
155+
156+
elif kl == 'collection':
157+
collection_name = v
158+
159+
else:
160+
raise CLIError(
161+
f'Unsupported Key {k} is provided for {option_string} component. All'
162+
' possible keys are: database, collection'
163+
)
164+
165+
if database_name is None:
166+
raise CLIError(f'usage error: missing key database in {option_string} component')
167+
168+
if collection_name is None:
169+
raise CLIError(f'usage error: missing key table in {option_string} component')
170+
171+
mongo_collection = CosmosMongoDataTransferDataSourceSink(database_name=database_name, collection_name=collection_name)
172+
173+
if option_string == "--source-mongo":
174+
namespace.source_mongo = mongo_collection
175+
elif option_string == "--dest-mongo":
176+
namespace.dest_mongo = mongo_collection
177+
else:
178+
namespace.mongo_collection = mongo_collection
179+
180+
141181
class AddSqlContainerAction(argparse._AppendAction):
142182
def __call__(self, parser, namespace, values, option_string=None):
143183
if not values:

src/cosmosdb-preview/azext_cosmosdb_preview/custom.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,33 +1114,51 @@ def cosmosdb_data_transfer_copy_job(client,
11141114
dest_cassandra_table=None,
11151115
source_sql_container=None,
11161116
dest_sql_container=None,
1117+
source_mongo=None,
1118+
dest_mongo=None,
11171119
worker_count=0,
11181120
job_name=None):
1119-
if source_cassandra_table is None and source_sql_container is None:
1120-
raise CLIError('source component ismissing')
1121-
1122-
if source_cassandra_table is not None and source_sql_container is not None:
1123-
raise CLIError('Invalid input: multiple source components')
1124-
1125-
if dest_cassandra_table is None and dest_sql_container is None:
1126-
raise CLIError('destination component is missing')
1127-
1128-
if dest_cassandra_table is not None and dest_sql_container is not None:
1129-
raise CLIError('Invalid input: multiple destination components')
1130-
11311121
job_create_properties = {}
11321122

1123+
source = None
11331124
if source_cassandra_table is not None:
1134-
job_create_properties['source'] = source_cassandra_table
1125+
if source is not None:
1126+
raise CLIError('Invalid input: multiple source components')
1127+
source = source_cassandra_table
11351128

11361129
if source_sql_container is not None:
1137-
job_create_properties['source'] = source_sql_container
1130+
if source is not None:
1131+
raise CLIError('Invalid input: multiple source components')
1132+
source = source_sql_container
11381133

1134+
if source_mongo is not None:
1135+
if source is not None:
1136+
raise CLIError('Invalid input: multiple source components')
1137+
source = source_mongo
1138+
1139+
if source is None:
1140+
raise CLIError('source component is missing')
1141+
job_create_properties['source'] = source
1142+
1143+
destination = None
11391144
if dest_cassandra_table is not None:
1140-
job_create_properties['destination'] = dest_cassandra_table
1145+
if destination is not None:
1146+
raise CLIError('Invalid input: multiple destination components')
1147+
destination = dest_cassandra_table
11411148

11421149
if dest_sql_container is not None:
1143-
job_create_properties['destination'] = dest_sql_container
1150+
if destination is not None:
1151+
raise CLIError('Invalid input: multiple destination components')
1152+
destination = dest_sql_container
1153+
1154+
if dest_mongo is not None:
1155+
if destination is not None:
1156+
raise CLIError('Invalid input: multiple destination components')
1157+
destination = dest_mongo
1158+
1159+
if destination is None:
1160+
raise CLIError('destination component is missing')
1161+
job_create_properties['destination'] = destination
11441162

11451163
if worker_count > 0:
11461164
job_create_properties['worker_count'] = worker_count

src/cosmosdb-preview/azext_cosmosdb_preview/vendored_sdks/azure_mgmt_cosmosdb/aio/operations/_cassandra_clusters_operations.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
88
# --------------------------------------------------------------------------
99
from typing import Any, AsyncIterable, Callable, Dict, IO, Optional, TypeVar, Union, cast, overload
10-
from urllib.parse import parse_qs, urljoin, urlparse
10+
import urllib.parse
1111

1212
from azure.core.async_paging import AsyncItemPaged, AsyncList
1313
from azure.core.exceptions import (
@@ -106,10 +106,17 @@ def prepare_request(next_link=None):
106106

107107
else:
108108
# make call to next link with the client's api-version
109-
_parsed_next_link = urlparse(next_link)
110-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
109+
_parsed_next_link = urllib.parse.urlparse(next_link)
110+
_next_request_params = case_insensitive_dict(
111+
{
112+
key: [urllib.parse.quote(v) for v in value]
113+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
114+
}
115+
)
111116
_next_request_params["api-version"] = self._config.api_version
112-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
117+
request = HttpRequest(
118+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
119+
)
113120
request = _convert_request(request)
114121
request.url = self._client.format_url(request.url) # type: ignore
115122
request.method = "GET"
@@ -184,10 +191,17 @@ def prepare_request(next_link=None):
184191

185192
else:
186193
# make call to next link with the client's api-version
187-
_parsed_next_link = urlparse(next_link)
188-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
194+
_parsed_next_link = urllib.parse.urlparse(next_link)
195+
_next_request_params = case_insensitive_dict(
196+
{
197+
key: [urllib.parse.quote(v) for v in value]
198+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
199+
}
200+
)
189201
_next_request_params["api-version"] = self._config.api_version
190-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
202+
request = HttpRequest(
203+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
204+
)
191205
request = _convert_request(request)
192206
request.url = self._client.format_url(request.url) # type: ignore
193207
request.method = "GET"
@@ -1067,10 +1081,17 @@ def prepare_request(next_link=None):
10671081

10681082
else:
10691083
# make call to next link with the client's api-version
1070-
_parsed_next_link = urlparse(next_link)
1071-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
1084+
_parsed_next_link = urllib.parse.urlparse(next_link)
1085+
_next_request_params = case_insensitive_dict(
1086+
{
1087+
key: [urllib.parse.quote(v) for v in value]
1088+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
1089+
}
1090+
)
10721091
_next_request_params["api-version"] = self._config.api_version
1073-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
1092+
request = HttpRequest(
1093+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
1094+
)
10741095
request = _convert_request(request)
10751096
request.url = self._client.format_url(request.url) # type: ignore
10761097
request.method = "GET"

src/cosmosdb-preview/azext_cosmosdb_preview/vendored_sdks/azure_mgmt_cosmosdb/aio/operations/_cassandra_data_centers_operations.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
88
# --------------------------------------------------------------------------
99
from typing import Any, AsyncIterable, Callable, Dict, IO, Optional, TypeVar, Union, cast, overload
10-
from urllib.parse import parse_qs, urljoin, urlparse
10+
import urllib.parse
1111

1212
from azure.core.async_paging import AsyncItemPaged, AsyncList
1313
from azure.core.exceptions import (
@@ -108,10 +108,17 @@ def prepare_request(next_link=None):
108108

109109
else:
110110
# make call to next link with the client's api-version
111-
_parsed_next_link = urlparse(next_link)
112-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
111+
_parsed_next_link = urllib.parse.urlparse(next_link)
112+
_next_request_params = case_insensitive_dict(
113+
{
114+
key: [urllib.parse.quote(v) for v in value]
115+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
116+
}
117+
)
113118
_next_request_params["api-version"] = self._config.api_version
114-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
119+
request = HttpRequest(
120+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
121+
)
115122
request = _convert_request(request)
116123
request.url = self._client.format_url(request.url) # type: ignore
117124
request.method = "GET"

src/cosmosdb-preview/azext_cosmosdb_preview/vendored_sdks/azure_mgmt_cosmosdb/aio/operations/_cassandra_resources_operations.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
88
# --------------------------------------------------------------------------
99
from typing import Any, AsyncIterable, Callable, Dict, IO, Optional, TypeVar, Union, cast, overload
10-
from urllib.parse import parse_qs, urljoin, urlparse
10+
import urllib.parse
1111

1212
from azure.core.async_paging import AsyncItemPaged, AsyncList
1313
from azure.core.exceptions import (
@@ -129,10 +129,17 @@ def prepare_request(next_link=None):
129129

130130
else:
131131
# make call to next link with the client's api-version
132-
_parsed_next_link = urlparse(next_link)
133-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
132+
_parsed_next_link = urllib.parse.urlparse(next_link)
133+
_next_request_params = case_insensitive_dict(
134+
{
135+
key: [urllib.parse.quote(v) for v in value]
136+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
137+
}
138+
)
134139
_next_request_params["api-version"] = self._config.api_version
135-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
140+
request = HttpRequest(
141+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
142+
)
136143
request = _convert_request(request)
137144
request.url = self._client.format_url(request.url) # type: ignore
138145
request.method = "GET"
@@ -1177,10 +1184,17 @@ def prepare_request(next_link=None):
11771184

11781185
else:
11791186
# make call to next link with the client's api-version
1180-
_parsed_next_link = urlparse(next_link)
1181-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
1187+
_parsed_next_link = urllib.parse.urlparse(next_link)
1188+
_next_request_params = case_insensitive_dict(
1189+
{
1190+
key: [urllib.parse.quote(v) for v in value]
1191+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
1192+
}
1193+
)
11821194
_next_request_params["api-version"] = self._config.api_version
1183-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
1195+
request = HttpRequest(
1196+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
1197+
)
11841198
request = _convert_request(request)
11851199
request.url = self._client.format_url(request.url) # type: ignore
11861200
request.method = "GET"
@@ -2266,10 +2280,17 @@ def prepare_request(next_link=None):
22662280

22672281
else:
22682282
# make call to next link with the client's api-version
2269-
_parsed_next_link = urlparse(next_link)
2270-
_next_request_params = case_insensitive_dict(parse_qs(_parsed_next_link.query))
2283+
_parsed_next_link = urllib.parse.urlparse(next_link)
2284+
_next_request_params = case_insensitive_dict(
2285+
{
2286+
key: [urllib.parse.quote(v) for v in value]
2287+
for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items()
2288+
}
2289+
)
22712290
_next_request_params["api-version"] = self._config.api_version
2272-
request = HttpRequest("GET", urljoin(next_link, _parsed_next_link.path), params=_next_request_params)
2291+
request = HttpRequest(
2292+
"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params
2293+
)
22732294
request = _convert_request(request)
22742295
request.url = self._client.format_url(request.url) # type: ignore
22752296
request.method = "GET"

0 commit comments

Comments
 (0)