Add Sync template feature to KB-client.
Existing feature "sync create" is to sync single resource-type to single target region i.e one resource off one resource-type to one region. Issue with "sync create": Kingbird cannot sync more than one resource-type to multiple target regions in a single request. This commit from kingbird client is to enhance the existing "sync create" job. Working of sync template feature: User have to provide a input file with .yaml or .yml or .json extension. This input file should consists of ->resource-type ->resources ->source_region ->target_region. Added testcases for the same. Change-Id: I5fb0e4619b20201585e5398013ba25219e24dc80
This commit is contained in:
parent
78c3169c4a
commit
90d71c0749
40
README.rst
40
README.rst
@ -80,13 +80,51 @@ You can see the list of available commands typing::
|
||||
|
||||
$ kingbird --help
|
||||
|
||||
About sync template command
|
||||
============================
|
||||
Provide the input file in .yaml/.yml/.json to sync multiple resource-types to multiple regions
|
||||
Sample input file for .yaml/.yml
|
||||
--------------------------------
|
||||
Eg::
|
||||
|
||||
Sync:
|
||||
- resource_type: fake_resource_type
|
||||
resources:
|
||||
- fake_resource_1
|
||||
- fake_resource_2
|
||||
source_region:
|
||||
- fake_source_region
|
||||
target_region:
|
||||
- fake_target_region_1
|
||||
- fake_target_region_2
|
||||
|
||||
|
||||
Sample input file for .json
|
||||
--------------------------
|
||||
Eg::
|
||||
|
||||
{
|
||||
"Sync": [
|
||||
{
|
||||
"resource_type": "fake_resource_type",
|
||||
"resources": [
|
||||
"fake_resource_1",
|
||||
"fake_resource_2"
|
||||
],
|
||||
"source_region":["fake_source_region"],
|
||||
"target_region":["fake_target_region_1","fake_target_region_2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Useful Links
|
||||
============
|
||||
* Free software: Apache license
|
||||
* `PyPi`_ - package installation
|
||||
* `Launchpad project`_ - release management
|
||||
* `Blueprints`_ - feature specifications
|
||||
* `Bugs`_ - issue tracking
|
||||
* `Bugs` - issue tracking
|
||||
* `Source`_
|
||||
* `How to Contribute`_
|
||||
* `Documentation`_
|
||||
|
@ -12,6 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import yaml
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
from kingbirdclient.commands.v1 import base
|
||||
@ -142,6 +145,72 @@ class ResourceSync(base.KingbirdLister):
|
||||
return kingbird_client.sync_manager.sync_resources(**kwargs)
|
||||
|
||||
|
||||
class TemplateResourceSync(base.KingbirdLister):
|
||||
"""Sync multiple resource-types to multiple regions."""
|
||||
|
||||
def _get_format_function(self):
|
||||
return sync_format
|
||||
|
||||
def get_parser(self, parsed_args):
|
||||
parser = super(TemplateResourceSync, self).get_parser(parsed_args)
|
||||
|
||||
parser.add_argument(
|
||||
'--template',
|
||||
required=True,
|
||||
help='Specify the name of an input file in .yaml/.yml/.json.'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def _get_resources(self, parsed_args):
|
||||
kingbird_client = self.app.client_manager.sync_engine
|
||||
kwargs = dict()
|
||||
sync_template = parsed_args.template
|
||||
if sync_template.endswith('.yaml') or sync_template.endswith('.yml') \
|
||||
or sync_template.endswith('.json'):
|
||||
try:
|
||||
if sync_template.endswith('.json'):
|
||||
with open(sync_template) as json_data:
|
||||
data = json.load(json_data)
|
||||
else:
|
||||
data = yaml.load(open(sync_template))
|
||||
except Exception:
|
||||
raise exceptions.TemplateError(
|
||||
'Syntactical errors in the template')
|
||||
else:
|
||||
raise exceptions.TemplateError(
|
||||
'Provide a template with a valid extension(.yaml/.yml/.json)')
|
||||
for iteration in data['Sync']:
|
||||
if 'source_region' not in iteration:
|
||||
raise exceptions.TemplateError(
|
||||
'source_region parameter is missing in template')
|
||||
if not iteration['source_region']:
|
||||
raise exceptions.TemplateError(
|
||||
'source_region parameter value is missing')
|
||||
if 'target_region' not in iteration:
|
||||
raise exceptions.TemplateError(
|
||||
'target_region parameter is missing in template')
|
||||
if not iteration['target_region']:
|
||||
raise exceptions.TemplateError(
|
||||
'target_region parameter value is missing')
|
||||
if 'resource_type' not in iteration:
|
||||
raise exceptions.TemplateError(
|
||||
'resource_type parameter is missing in template')
|
||||
if not iteration['resource_type']:
|
||||
raise exceptions.TemplateError(
|
||||
'resource_type parameter value is missing')
|
||||
if 'resources' not in iteration:
|
||||
raise exceptions.TemplateError(
|
||||
'resources parameter is missing in template')
|
||||
if not iteration['resources']:
|
||||
raise exceptions.TemplateError(
|
||||
'resources parameter value is missing')
|
||||
|
||||
kwargs.update(data)
|
||||
|
||||
return kingbird_client.sync_manager.sync_resources(**kwargs)
|
||||
|
||||
|
||||
class SyncList(base.KingbirdLister):
|
||||
"""List Sync Jobs."""
|
||||
|
||||
|
@ -54,3 +54,12 @@ class APIException(Exception):
|
||||
super(APIException, self).__init__(error_message)
|
||||
self.error_code = error_code
|
||||
self.error_message = error_message
|
||||
|
||||
|
||||
class TemplateError(KingbirdClientException):
|
||||
message = "Insufficient parameters in the template"
|
||||
code = "TEMPLATE_ERROR_EXCEPTION"
|
||||
|
||||
def __init__(self, message=None):
|
||||
if message:
|
||||
self.message = message
|
||||
|
@ -471,6 +471,7 @@ class KingbirdShell(app.App):
|
||||
'sync create': sm.ResourceSync,
|
||||
'sync list': sm.SyncList,
|
||||
'sync show': sm.SyncShow,
|
||||
'sync template': sm.TemplateResourceSync,
|
||||
'sync delete': sm.SyncDelete,
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,13 @@
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
|
||||
import os
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from kingbirdclient.api.v1 import sync_manager as sm
|
||||
from kingbirdclient.commands.v1 import sync_manager as sync_cmd
|
||||
from kingbirdclient import exceptions
|
||||
from kingbirdclient.tests import base
|
||||
|
||||
TIME_NOW = timeutils.utcnow().isoformat()
|
||||
@ -31,6 +32,41 @@ FAKE_SOURCE_REGION = 'fake_region_1'
|
||||
FAKE_TARGET_REGION = 'fake_region_2'
|
||||
FAKE_RESOURCE_TYPE = 'fake_resource'
|
||||
|
||||
tempdef = """Sync:
|
||||
- resource_type: fake_resource_type
|
||||
resources:
|
||||
- fake_resource_1
|
||||
- fake_resource_2
|
||||
source_region:
|
||||
- fake_source_region
|
||||
target_region:
|
||||
- fake_target_region_1
|
||||
- fake_target_region_2
|
||||
"""
|
||||
RESOURCE_TYPE_INDEX = tempdef.index('resource_type:')
|
||||
RESOURCE_INDEX = tempdef.index('resources:')
|
||||
SOURCE_INDEX = tempdef.index('source_region:')
|
||||
TARGET_INDEX = tempdef.index('target_region:')
|
||||
|
||||
tempdefjson = """{
|
||||
"Sync": [
|
||||
{
|
||||
"resource_type": "fake_resource_type",
|
||||
"resources": [
|
||||
"fake_resource_1",
|
||||
"fake_resource_2"
|
||||
],
|
||||
"source_region":["fake_source_region"],
|
||||
"target_region":["fake_target_region_1","fake_target_region_2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
RESOURCE_TYPE_INDEX_JSON = tempdefjson.index('"resource_type"')
|
||||
RESOURCE_INDEX_JSON = tempdefjson.index('"resources"')
|
||||
SOURCE_INDEX_JSON = tempdefjson.index('"source_region"')
|
||||
TARGET_INDEX_JSON = tempdefjson.index(",", SOURCE_INDEX_JSON)
|
||||
|
||||
RESOURCE_DICT = {
|
||||
'ID': ID,
|
||||
'STATUS': FAKE_STATUS,
|
||||
@ -80,6 +116,7 @@ SYNC_RESOURCEMANAGER = sm.Resource(mock, id=RESOURCE_DICT['ID'],
|
||||
|
||||
|
||||
class TestCLISyncManagerV1(base.BaseCommandTest):
|
||||
"""Testcases for sync command."""
|
||||
|
||||
def test_sync_jobs_list(self):
|
||||
self.client.sync_manager.list_sync_jobs.return_value = [SYNCMANAGER]
|
||||
@ -196,3 +233,146 @@ class TestCLISyncManagerV1(base.BaseCommandTest):
|
||||
'--target', FAKE_TARGET_REGION,
|
||||
'--force'])
|
||||
self.assertEqual([(ID, FAKE_STATUS, TIME_NOW)], actual_call[1])
|
||||
|
||||
def test_template_resource_sync_with_template_yaml(self):
|
||||
with open('test_template.yaml', 'w') as f:
|
||||
f.write(tempdef)
|
||||
f.close()
|
||||
self.client.sync_manager.sync_resources.\
|
||||
return_value = [SYNC_RESOURCEMANAGER]
|
||||
actual_call = self.call(
|
||||
sync_cmd.TemplateResourceSync, app_args=[
|
||||
'--template', 'test_template.yaml'])
|
||||
self.assertEqual([(ID, FAKE_STATUS, TIME_NOW)], actual_call[1])
|
||||
os.remove("test_template.yaml")
|
||||
|
||||
def test_template_resource_sync_with_template_yml(self):
|
||||
with open('test_template.yml', 'w') as f:
|
||||
f.write(tempdef)
|
||||
f.close()
|
||||
self.client.sync_manager.sync_resources.\
|
||||
return_value = [SYNC_RESOURCEMANAGER]
|
||||
actual_call = self.call(
|
||||
sync_cmd.TemplateResourceSync, app_args=[
|
||||
'--template', 'test_template.yml'])
|
||||
self.assertEqual([(ID, FAKE_STATUS, TIME_NOW)], actual_call[1])
|
||||
os.remove("test_template.yml")
|
||||
|
||||
def test_template_resource_sync_with_template_json(self):
|
||||
with open('test_template.json', 'w') as f:
|
||||
f.write(tempdefjson)
|
||||
f.close()
|
||||
self.client.sync_manager.sync_resources.\
|
||||
return_value = [SYNC_RESOURCEMANAGER]
|
||||
actual_call = self.call(
|
||||
sync_cmd.TemplateResourceSync, app_args=[
|
||||
'--template', 'test_template.json'])
|
||||
self.assertEqual([(ID, FAKE_STATUS, TIME_NOW)], actual_call[1])
|
||||
os.remove("test_template.json")
|
||||
|
||||
def test_template_resource_sync_without_template(self):
|
||||
self.client.sync_manager.sync_resources.\
|
||||
return_value = [SYNC_RESOURCEMANAGER]
|
||||
self.assertRaises(
|
||||
SystemExit, self.call, sync_cmd.TemplateResourceSync, app_args=[])
|
||||
|
||||
def test_template_resource_sync_invalid_extension(self):
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_template.yzx'])
|
||||
|
||||
def test_template_resource_sync_source_missing_yaml(self):
|
||||
temp = tempdef.replace(tempdef[SOURCE_INDEX:TARGET_INDEX], "")
|
||||
with open('test_source_missing_template.yaml', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_source_missing_template.yaml'])
|
||||
os.remove("test_source_missing_template.yaml")
|
||||
|
||||
def test_template_resource_sync_target_missing_yaml(self):
|
||||
temp = tempdef.replace(tempdef[TARGET_INDEX:], "")
|
||||
with open('test_target_missing_template.yaml', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_target_missing_template.yaml'])
|
||||
os.remove("test_target_missing_template.yaml")
|
||||
|
||||
def test_template_resource_sync_resource_type_missing_yaml(self):
|
||||
temp = tempdef.replace(tempdef[RESOURCE_TYPE_INDEX:RESOURCE_INDEX], "")
|
||||
with open('test_resource_type_missing_template.yaml', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template',
|
||||
'test_resource_type_missing_template.yaml'])
|
||||
os.remove("test_resource_type_missing_template.yaml")
|
||||
|
||||
def test_template_resource_sync_resources_missing_yaml(self):
|
||||
temp = tempdef.replace(tempdef[RESOURCE_INDEX:SOURCE_INDEX], "")
|
||||
with open('test_resource_missing_template.yaml', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_resource_missing_template.yaml'])
|
||||
os.remove("test_resource_missing_template.yaml")
|
||||
|
||||
def test_template_resource_sync_source_missing_json(self):
|
||||
temp = tempdefjson.replace(
|
||||
tempdefjson[SOURCE_INDEX_JSON:TARGET_INDEX_JSON + 1], "")
|
||||
with open('test_source_missing_template.json', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template',
|
||||
'test_source_missing_template.json'])
|
||||
os.remove("test_source_missing_template.json")
|
||||
|
||||
def test_template_resource_sync_target_missing_json(self):
|
||||
temp = tempdefjson.replace(
|
||||
tempdefjson[TARGET_INDEX_JSON:tempdefjson.index("}")], "")
|
||||
with open('test_target_missing_template.json', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_target_missing_template.json'])
|
||||
os.remove("test_target_missing_template.json")
|
||||
|
||||
def test_template_resource_sync_resource_type_missing_json(self):
|
||||
temp = tempdefjson.replace(
|
||||
tempdefjson[RESOURCE_TYPE_INDEX_JSON:RESOURCE_INDEX_JSON], "")
|
||||
with open('test_resource_type_missing_template.json', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template',
|
||||
'test_resource_type_missing_template.json'])
|
||||
os.remove("test_resource_type_missing_template.json")
|
||||
|
||||
def test_template_resource_sync_resources_missing_json(self):
|
||||
temp = tempdefjson.replace(
|
||||
tempdefjson[RESOURCE_INDEX_JSON:SOURCE_INDEX_JSON], "")
|
||||
with open('test_resource_missing_template.json', 'w') as f:
|
||||
f.write(temp)
|
||||
f.close()
|
||||
self.assertRaises(
|
||||
exceptions.TemplateError, self.call,
|
||||
sync_cmd.TemplateResourceSync,
|
||||
app_args=['--template', 'test_resource_missing_template.json'])
|
||||
os.remove("test_resource_missing_template.json")
|
||||
|
Loading…
Reference in New Issue
Block a user