Add option to get merged facts for environment

Since 9.1 the nailgun returns separated facts for cluster
and nodes, so some client expects that node facts also
contains cluster fact. this patch adds possibility
to get merged facts for each node, by default this flag is true

Change-Id: I29f0cc0090abca2826e273dcbe7ac4249cf7a731
Closes-Bug: 1623854
This commit is contained in:
Bulat Gaifullin 2016-09-15 17:56:41 +03:00
parent 253d7ebc25
commit 330b10a052
8 changed files with 97 additions and 62 deletions

View File

@ -47,6 +47,7 @@ class FactAction(Action):
Args.get_node_arg(
"Node ids."
),
Args.get_not_split_facts_args(),
]
self.flag_func_map = (
("default", self.default),
@ -66,7 +67,9 @@ class FactAction(Action):
env = Environment(params.env)
dir_name = env.write_facts_to_dir(
self.action_name,
env.get_default_facts(self.action_name, nodes=params.node),
env.get_default_facts(
self.action_name, nodes=params.node, split=params.split
),
directory=params.dir,
serializer=self.serializer
)
@ -107,7 +110,9 @@ class FactAction(Action):
env = Environment(params.env)
dir_name = env.write_facts_to_dir(
self.action_name,
env.get_facts(self.action_name, nodes=params.node),
env.get_facts(
self.action_name, nodes=params.node, split=params.split
),
directory=params.dir,
serializer=self.serializer
)

View File

@ -775,3 +775,13 @@ def get_include_summary_arg(help_msg):
"help": help_msg
}
return get_boolean_arg("include-summary", **default_kwargs)
def get_not_split_facts_args():
kwargs = {
"action": "store_false",
"default": True,
"dest": "split",
"help": "Do not split deployment info for node and cluster parts."
}
return get_arg('no-split', **kwargs)

View File

@ -720,13 +720,9 @@ class FactsMixIn(object):
_fact)
def download(self, env_id, fact_type, destination_dir, file_format,
nodes=None, default=False):
nodes=None, default=False, split=None):
facts = self.client.download_facts(
env_id, fact_type, nodes=nodes, default=default)
if not facts:
raise error.ServerDataException(
"There are no {} facts for this environment!".format(
fact_type))
env_id, fact_type, nodes=nodes, default=default, split=split)
facts_dir = self._get_fact_dir(env_id, fact_type, destination_dir)
if os.path.exists(facts_dir):
@ -824,6 +820,14 @@ class BaseEnvFactsDownload(FactsMixIn, EnvMixIn, base.BaseCommand):
required=True,
help='Format of serialized {} facts'.format(self.fact_type))
parser.add_argument(
'--no-split',
action='store_false',
dest='split',
default=True,
help='Do not split deployment info for node and cluster parts.'
)
return parser
def take_action(self, parsed_args):
@ -833,7 +837,8 @@ class BaseEnvFactsDownload(FactsMixIn, EnvMixIn, base.BaseCommand):
parsed_args.directory,
parsed_args.format,
nodes=parsed_args.nodes,
default=self.fact_default
default=self.fact_default,
split=parsed_args.split
)
self.app.stdout.write(
"{0} {1} facts for the environment {2} "

View File

@ -296,37 +296,34 @@ class Environment(BaseObject):
os.path.abspath(directory),
"{0}_{1}".format(fact_type, self.id))
def _get_fact_default_url(self, fact_type, nodes=None):
default_url = "clusters/{0}/orchestrator/{1}/defaults".format(
self.id,
fact_type
def _get_fact_url(self, fact_type, default=False):
fact_url = "clusters/{0}/orchestrator/{1}/{2}".format(
self.id, fact_type, 'defaults/' if default else ''
)
if nodes is not None:
default_url += "/?nodes=" + ",".join(map(str, nodes))
return default_url
def _get_fact_url(self, fact_type, nodes=None):
fact_url = "clusters/{0}/orchestrator/{1}/".format(
self.id,
fact_type
)
if nodes is not None:
fact_url += "/?nodes=" + ",".join(map(str, nodes))
return fact_url
def get_default_facts(self, fact_type, nodes=None):
facts = self.connection.get_request(
self._get_fact_default_url(fact_type, nodes=nodes))
if not facts:
raise error.ServerDataException(
"There is no {0} info for this "
"environment!".format(fact_type)
)
return facts
def get_default_facts(self, fact_type, **kwargs):
"""Gets default facts for cluster.
:param fact_type: the type of facts (deployment, provision)
"""
return self.get_facts(fact_type, default=True, **kwargs)
def get_facts(self, fact_type, default=False, nodes=None, split=None):
"""Gets facts for cluster.
:param fact_type: the type of facts (deployment, provision)
:param default: if True, the default facts will be retrieved
:param nodes: if specified, get facts only for selected nodes
:param split: if True, the node part and common part will be split
"""
params = {}
if nodes is not None:
params['nodes'] = ','.join(str(x) for x in nodes)
if split is not None:
params['split'] = str(int(split))
def get_facts(self, fact_type, nodes=None):
facts = self.connection.get_request(
self._get_fact_url(fact_type, nodes=nodes))
self._get_fact_url(fact_type, default=default), params=params
)
if not facts:
raise error.ServerDataException(
"There is no {0} info for this "

View File

@ -170,3 +170,25 @@ class TestEnvironmentOstf(base.UnitTestCase):
self.env.get_deployment_tasks(end=end)
self.assertEqual(get.last_request.qs, {'end': ['task1']})
def test_get_default_facts(self):
legacy_format_facts = [
{"uid": "1", "a": 1}, {"uid": "2", "a": 1}
]
new_format_facts = [
{"uid": "common", "a": 1}, {"uid": "1"}, {"uid": "2"}
]
self.m_request.get(
'/api/v1/clusters/{0}/orchestrator/deployment/defaults/?split=0'
.format(self.env.id),
json=legacy_format_facts
)
self.m_request.get(
'/api/v1/clusters/{0}/orchestrator/deployment/defaults/?split=1'
.format(self.env.id),
json=new_format_facts
)
facts = self.env.get_default_facts("deployment", split=False)
self.assertItemsEqual(legacy_format_facts, facts)
facts = self.env.get_default_facts("deployment", split=True)
self.assertItemsEqual(new_format_facts, facts)

View File

@ -503,8 +503,11 @@ class TestEnvCommand(test_engine.BaseCLITest):
@mock.patch('json.dump')
def _deployment_facts_download_json(self, m_dump, default=False):
command = 'get-default' if default else 'download'
args = "env deployment-facts {}" \
" --env 42 --dir /tmp --nodes 2 --format json".format(command)
args = (
"env deployment-facts {}"
" --env 42 --dir /tmp --nodes 2 --format json --no-split"
.format(command)
)
data = [{'uid': 2, 'name': 'node'}]
expected_path = '/tmp/deployment_42/2.json'
@ -517,7 +520,7 @@ class TestEnvCommand(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('environment', mock.ANY)
self.m_client.download_facts.assert_called_once_with(
42, 'deployment', nodes=[2], default=default)
42, 'deployment', nodes=[2], default=default, split=False)
m_open.assert_called_once_with(expected_path, 'w')
m_dump.assert_called_once_with(data[0], mock.ANY, indent=4)
@ -530,8 +533,11 @@ class TestEnvCommand(test_engine.BaseCLITest):
@mock.patch('yaml.safe_dump')
def _deployment_facts_download_yaml(self, m_safe_dump, default=False):
command = 'get-default' if default else 'download'
args = "env deployment-facts {}" \
" --env 42 --dir /tmp --nodes 2 --format yaml".format(command)
args = (
"env deployment-facts {}"
" --env 42 --dir /tmp --nodes 2 --format yaml"
.format(command)
)
data = [{'uid': 2, 'name': 'node'}]
expected_path = '/tmp/deployment_42/2.yaml'
@ -544,7 +550,7 @@ class TestEnvCommand(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('environment', mock.ANY)
self.m_client.download_facts.assert_called_once_with(
42, 'deployment', nodes=[2], default=default)
42, 'deployment', nodes=[2], default=default, split=True)
m_open.assert_called_once_with(expected_path, 'w')
m_safe_dump.assert_called_once_with(data[0], mock.ANY,
default_flow_style=False)
@ -608,7 +614,7 @@ class TestEnvCommand(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('environment', mock.ANY)
self.m_client.download_facts.assert_called_once_with(
42, 'provisioning', nodes=[2], default=default)
42, 'provisioning', nodes=[2], default=default, split=True)
m_open.assert_any_call(expected_path_engine, 'w')
m_dump.assert_any_call(data['engine'], mock.ANY, indent=4)
m_open.assert_any_call(expected_path_node, 'w')
@ -641,7 +647,7 @@ class TestEnvCommand(test_engine.BaseCLITest):
self.m_get_client.assert_called_once_with('environment', mock.ANY)
self.m_client.download_facts.assert_called_once_with(
42, 'provisioning', nodes=[2], default=default)
42, 'provisioning', nodes=[2], default=default, split=True)
m_open.assert_any_call(expected_path_engine, 'w')
m_dump.assert_any_call(data['engine'], mock.ANY,
default_flow_style=False)

View File

@ -487,7 +487,7 @@ class TestEnvFacade(test_api.BaseLibTest):
expected_uri = self.get_object_uri(
self.res_uri,
env_id,
'/orchestrator/{fact_type}'.format(fact_type=fact_type))
'/orchestrator/{fact_type}/'.format(fact_type=fact_type))
matcher = self.m_request.delete(expected_uri, json={})
self.client.delete_facts(env_id, fact_type)
@ -519,7 +519,7 @@ class TestEnvFacade(test_api.BaseLibTest):
expected_uri = self.get_object_uri(
self.res_uri,
env_id,
"/orchestrator/{fact_type}".format(fact_type=fact_type))
"/orchestrator/{fact_type}/".format(fact_type=fact_type))
matcher = self.m_request.put(expected_uri, json={})
self.client.upload_facts(env_id, fact_type, facts)

View File

@ -163,27 +163,17 @@ class EnvironmentClient(base_v1.BaseV1Client):
env = self._entity_wrapper(environment_id)
env.set_settings_data(new_configuration, force=force)
@staticmethod
def _get_fact_url(env_id, fact_type, nodes=None, default=False):
return "clusters/{id}/orchestrator/{fact_type}{default}{nodes}".format(
id=env_id,
fact_type=fact_type,
default="/defaults" if default else '',
nodes="/?nodes={}".format(
",".join(map(str, nodes))) if nodes else ''
)
def delete_facts(self, env_id, fact_type):
return self.connection.delete_request(
self._get_fact_url(env_id, fact_type))
env = self._entity_wrapper(env_id)
return env.delete_facts(fact_type)
def download_facts(self, env_id, fact_type, nodes=None, default=False):
return self.connection.get_request(self._get_fact_url(
env_id, fact_type, nodes=nodes, default=default))
def download_facts(self, env_id, fact_type, default=False, **kwargs):
env = self._entity_wrapper(env_id)
return env.get_facts(fact_type, default=default, **kwargs)
def upload_facts(self, env_id, fact_type, facts):
return self.connection.put_request(
self._get_fact_url(env_id, fact_type), facts)
env = self._entity_wrapper(env_id)
return env.upload_facts(fact_type, facts)
def reset(self, env_id, force=False):
env = self._entity_wrapper(obj_id=env_id)