Files
python-fuelclient/fuelclient/objects/environment.py
Igor Kalnitsky fa053d0768 Add 'force' param for DeploySelectedNodesWithTasks
DeploySelectedNodesWithTasks is intended to run only specified set of
tasks on specified nodes. In Fuel 9.0 LCM serializers have been
introduced, hence tasks are skipped if their condition return 'False'
(and it does, since no changes may introduced).

This introduces inconvenience for operators, who want to run particular
task on nodes and do not care if there were changes or not. That's why
this commit introduces the '?force' HTTP parameter for the mentioned
handler. If 'force' is true, the task will be executed unconditionally.

(cherry picked from commit 8bfb0255a1)

Closes-Bug: #1578974

Depends-On: Icbaa2bd72c5ff2fc128a0dab4b22d6db77dd36fb
Change-Id: Iaf005341468a4e35cd82586ba3f7b585fdf4752c
2016-05-12 14:32:31 +00:00

656 lines
22 KiB
Python

# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from operator import attrgetter
import os
import shutil
from fuelclient.cli import error
from fuelclient.cli.serializers import listdir_without_extensions
from fuelclient.objects.base import BaseObject
from fuelclient.objects.task import DeployTask
from fuelclient.objects.task import Task
class Environment(BaseObject):
class_api_path = "clusters/"
instance_api_path = "clusters/{0}/"
deployment_tasks_path = 'clusters/{0}/deployment_tasks'
deployment_tasks_graph_path = 'clusters/{0}/deploy_tasks/graph.gv'
attributes_path = 'clusters/{0}/attributes'
network_template_path = 'clusters/{0}/network_configuration/template'
@classmethod
def create(cls, name, release_id, net_segment_type):
data = {
"nodes": [],
"tasks": [],
"name": name,
"release_id": release_id,
"net_segment_type": net_segment_type,
}
data = cls.connection.post_request("clusters/", data)
return cls.init_with_data(data)
def __init__(self, *args, **kwargs):
super(Environment, self).__init__(*args, **kwargs)
self._testruns_ids = []
def set(self, data):
return self.connection.put_request(
"clusters/{0}/".format(self.id),
data
)
def delete(self):
return self.connection.delete_request(
"clusters/{0}/".format(self.id)
)
def assign(self, nodes, roles):
return self.connection.post_request(
"clusters/{0}/assignment/".format(self.id),
[{'id': node.id, 'roles': roles} for node in nodes]
)
def unassign(self, nodes):
return self.connection.post_request(
"clusters/{0}/unassignment/".format(self.id),
[{"id": n} for n in nodes]
)
def get_all_nodes(self):
from fuelclient.objects.node import Node
return sorted(map(
Node.init_with_data,
self.connection.get_request(
"nodes/?cluster_id={0}".format(self.id)
)
), key=attrgetter)
def unassign_all(self):
nodes = self.get_all_nodes()
if not nodes:
raise error.ActionException(
"Environment with id={0} doesn't have nodes to remove."
.format(self.id)
)
return self.connection.post_request(
"clusters/{0}/unassignment/".format(self.id),
[{"id": n.id} for n in nodes]
)
def deploy_changes(self):
deploy_data = self.connection.put_request(
"clusters/{0}/changes".format(self.id),
{}
)
return DeployTask.init_with_data(deploy_data)
def redeploy_changes(self):
deploy_data = self.connection.put_request(
"clusters/{0}/changes/redeploy".format(self.id),
{}
)
return DeployTask.init_with_data(deploy_data)
def get_network_data_path(self, directory=os.curdir):
return os.path.join(
os.path.abspath(directory),
"network_{0}".format(self.id)
)
def get_settings_data_path(self, directory=os.curdir):
return os.path.join(
os.path.abspath(directory),
"settings_{0}".format(self.id)
)
def get_vmware_settings_data_path(self, directory=os.curdir):
return os.path.join(
os.path.abspath(directory),
"vmware_settings_{0}".format(self.id)
)
def get_network_template_data_path(self, directory=None):
directory = directory or os.curdir
return os.path.join(
os.path.abspath(directory),
"network_template_{0}".format(self.id)
)
def write_network_data(self, network_data, directory=os.curdir,
serializer=None):
self._check_dir(directory)
return (serializer or self.serializer).write_to_path(
self.get_network_data_path(directory),
network_data
)
def write_settings_data(self, settings_data, directory=os.curdir,
serializer=None):
self._check_dir(directory)
return (serializer or self.serializer).write_to_path(
self.get_settings_data_path(directory),
settings_data
)
def write_vmware_settings_data(self, settings_data, directory=None,
serializer=None):
directory = directory or os.curdir
self._check_dir(directory)
return (serializer or self.serializer).write_to_path(
self.get_vmware_settings_data_path(directory),
settings_data
)
def write_network_template_data(self, template_data, directory=None,
serializer=None):
directory = directory or os.curdir
return (serializer or self.serializer).write_to_path(
self.get_network_template_data_path(directory),
template_data
)
def read_network_data(self, directory=os.curdir,
serializer=None):
self._check_dir(directory)
network_file_path = self.get_network_data_path(directory)
return (serializer or self.serializer).read_from_file(
network_file_path)
def read_settings_data(self, directory=os.curdir, serializer=None):
self._check_dir(directory)
settings_file_path = self.get_settings_data_path(directory)
return (serializer or self.serializer).read_from_file(
settings_file_path)
def _check_file_path(self, file_path):
if not os.path.exists(file_path):
raise error.InvalidFileException(
"File '{0}' doesn't exist.".format(file_path))
def _check_dir(self, directory):
if not os.path.exists(directory):
raise error.InvalidDirectoryException(
"Directory '{0}' doesn't exist.".format(directory))
if not os.path.isdir(directory):
raise error.InvalidDirectoryException(
"Error: '{0}' is not a directory.".format(directory))
def read_vmware_settings_data(self, directory=os.curdir, serializer=None):
self._check_dir(directory)
return (serializer or self.serializer).read_from_file(
self.get_vmware_settings_data_path(directory))
def read_network_template_data(self, directory=os.curdir,
serializer=None):
"""Used by 'fuel' command line utility."""
self._check_dir(directory)
network_template_file_path = self.get_network_template_data_path(
directory)
return self.read_network_template_data_from_file(
network_template_file_path, serializer)
def read_network_template_data_from_file(self, file_path=None,
serializer=None):
"""Used by 'fuel2' command line utility."""
return (serializer or self.serializer).\
read_from_file(file_path)
@property
def status(self):
return self.get_fresh_data()['status']
@property
def settings_url(self):
return self.attributes_path.format(self.id)
@property
def default_settings_url(self):
return self.settings_url + "/defaults"
@property
def vmware_settings_url(self):
return "clusters/{0}/vmware_attributes".format(self.id)
@property
def default_vmware_settings_url(self):
return self.vmware_settings_url + "/defaults"
@property
def network_url(self):
return "clusters/{id}/network_configuration/neutron".format(
**self.data
)
@property
def network_template_url(self):
return self.network_template_path.format(self.id)
@property
def network_verification_url(self):
return self.network_url + "/verify"
def get_network_data(self):
return self.connection.get_request(self.network_url)
def get_settings_data(self):
return self.connection.get_request(self.settings_url)
def get_default_settings_data(self):
return self.connection.get_request(self.default_settings_url)
def get_vmware_settings_data(self):
return self.connection.get_request(self.vmware_settings_url)
def get_default_vmware_settings_data(self):
return self.connection.get_request(self.default_vmware_settings_url)
def get_network_template_data(self):
return self.connection.get_request(self.network_template_url)
def set_network_data(self, data):
return self.connection.put_request(
self.network_url, data)
def set_settings_data(self, data, force=False):
if force:
result = self.connection.put_request(
self.settings_url, data, force=1)
else:
result = self.connection.put_request(
self.settings_url, data)
return result
def set_vmware_settings_data(self, data):
return self.connection.put_request(
self.vmware_settings_url, data)
def verify_network(self):
return self.connection.put_request(
self.network_verification_url, self.get_network_data())
def set_network_template_data(self, data):
return self.connection.put_request(
self.network_template_url, data)
def delete_network_template_data(self):
return self.connection.delete_request(self.network_template_url)
def _get_fact_dir_name(self, fact_type, directory=os.path.curdir):
return os.path.join(
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
)
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_facts(self, fact_type, nodes=None):
facts = self.connection.get_request(
self._get_fact_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 upload_facts(self, fact_type, facts):
self.connection.put_request(self._get_fact_url(fact_type), facts)
def delete_facts(self, fact_type):
self.connection.delete_request(self._get_fact_url(fact_type))
def read_fact_info(self, fact_type, directory, serializer=None):
return getattr(
self, "read_{0}_info".format(fact_type)
)(fact_type, directory=directory, serializer=serializer)
def write_facts_to_dir(self, fact_type, facts,
directory=os.path.curdir, serializer=None):
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
os.makedirs(dir_name)
if isinstance(facts, dict):
engine_file_path = os.path.join(dir_name, "engine")
(serializer or self.serializer).write_to_path(
engine_file_path, facts["engine"])
facts = facts["nodes"]
def name_builder(fact):
return fact['name']
else:
def name_builder(fact):
if 'role' in fact:
# from 9.0 the deployment info is serialized only per node
return "{role}_{uid}".format(**fact)
return fact['uid']
for _fact in facts:
fact_path = os.path.join(
dir_name,
name_builder(_fact)
)
(serializer or self.serializer).write_to_path(fact_path, _fact)
return dir_name
def read_deployment_info(self, fact_type,
directory=os.path.curdir, serializer=None):
self._check_dir(directory)
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
self._check_dir(dir_name)
return map(
lambda f: (serializer or self.serializer).read_from_file(f),
[os.path.join(dir_name, json_file)
for json_file in listdir_without_extensions(dir_name)]
)
def read_provisioning_info(self, fact_type,
directory=os.path.curdir, serializer=None):
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
node_facts = map(
lambda f: (serializer or self.serializer).read_from_file(f),
[os.path.join(dir_name, fact_file)
for fact_file in listdir_without_extensions(dir_name)
if "engine" != fact_file]
)
engine = (serializer or self.serializer).read_from_file(
os.path.join(dir_name, "engine"))
return {
"engine": engine,
"nodes": node_facts
}
def get_testsets(self):
return self.connection.get_request(
'testsets/{0}'.format(self.id),
ostf=True
)
@property
def is_customized(self):
data = self.get_fresh_data()
return data["is_customized"]
def is_in_running_test_sets(self, test_set):
return test_set["testset"] in self._test_sets_to_run
def run_test_sets(self, test_sets_to_run):
self._test_sets_to_run = test_sets_to_run
tests_data = map(
lambda testset: {
"testset": testset,
"metadata": {
"config": {},
"cluster_id": self.id
}
},
test_sets_to_run
)
testruns = self.connection.post_request(
"testruns", tests_data, ostf=True)
self._testruns_ids = [tr['id'] for tr in testruns]
return testruns
def get_state_of_tests(self):
return [
self.connection.get_request(
"testruns/{0}".format(testrun_id), ostf=True)
for testrun_id in self._testruns_ids
]
def stop(self):
return Task.init_with_data(
self.connection.put_request(
"clusters/{0}/stop_deployment/".format(self.id),
{}
)
)
def reset(self):
return Task.init_with_data(
self.connection.put_request(
"clusters/{0}/reset/".format(self.id),
{}
)
)
def _get_method_url(self, method_type, nodes, force=False):
endpoint = "clusters/{0}/{1}/?nodes={2}".format(
self.id,
method_type,
','.join(map(lambda n: str(n.id), nodes)))
if force:
endpoint += '&force=1'
return endpoint
def install_selected_nodes(self, method_type, nodes):
return Task.init_with_data(
self.connection.put_request(
self._get_method_url(method_type, nodes),
{}
)
)
def execute_tasks(self, nodes, tasks, force):
return Task.init_with_data(
self.connection.put_request(
self._get_method_url('deploy_tasks', nodes=nodes, force=force),
tasks
)
)
def get_tasks(self, skip=None, end=None, start=None, include=None):
"""Stores logic to filter tasks by known parameters.
:param skip: list of tasks or None
:param end: string or None
:param start: string or None
:param include: list or None
"""
tasks = [t['id'] for t in self.get_deployment_tasks(
end=end, start=start, include=include)]
if skip:
tasks_to_execute = set(tasks) - set(skip)
return list(tasks_to_execute)
return tasks
def get_deployment_tasks(self, end=None, start=None, include=None):
url = self.deployment_tasks_path.format(self.id)
return self.connection.get_request(
url, params={
'end': end,
'start': start,
'include': include})
def update_deployment_tasks(self, data):
url = self.deployment_tasks_path.format(self.id)
return self.connection.put_request(url, data)
def get_attributes(self):
return self.connection.get_request(self.settings_url)
def update_attributes(self, data, force=False):
if force:
result = self.connection.put_request(
self.settings_url, data, force=1)
else:
result = self.connection.put_request(
self.settings_url, data)
return result
def get_deployment_tasks_graph(self, tasks, parents_for=None, remove=None):
url = self.deployment_tasks_graph_path.format(self.id)
params = {
'tasks': ','.join(tasks),
'parents_for': parents_for,
'remove': ','.join(remove) if remove else None,
}
resp = self.connection.get_request_raw(url, params=params)
resp.raise_for_status()
return resp.text
def spawn_vms(self):
url = 'clusters/{0}/spawn_vms/'.format(self.id)
return self.connection.put_request(url, {})
def _get_ip_addrs_url(self, vips=True, ip_addr_id=None):
"""Generate ip address management url.
:param vips: manage vip properties of ip address
:type vips: bool
:param ip_addr_id: ip address identifier
:type ip_addr_id: int
:return: url
:rtype: str
"""
ip_addr_url = "clusters/{0}/network_configuration/ips/".format(self.id)
if ip_addr_id:
ip_addr_url += '{0}/'.format(ip_addr_id)
if vips:
ip_addr_url += 'vips/'
return ip_addr_url
def get_default_vips_data_path(self):
"""Get path where VIPs data is located.
:return: path
:rtype: str
"""
return os.path.join(
os.path.abspath(os.curdir),
"vips_{0}".format(self.id)
)
def get_vips_data(self, ip_address_id=None, network=None,
network_role=None):
"""Get one or multiple vip data records.
:param ip_address_id: ip addr id could be specified to download single
vip if no ip_addr_id specified multiple entities is
returned respecting network and network_role
filters
:type ip_address_id: int
:param network: network id could be specified to filter vips
:type network: int
:param network_role: network role could be specified to filter vips
:type network_role: string
:return: response JSON
:rtype: list of dict
"""
params = {}
if network:
params['network'] = network
if network_role:
params['network-role'] = network_role
result = self.connection.get_request(
self._get_ip_addrs_url(True, ip_addr_id=ip_address_id),
params=params
)
if ip_address_id is not None: # single vip is returned
# wrapping with list is required to respect case when administrator
# is downloading vip address info to change it and upload
# back. Uploading works only with lists of records.
result = [result]
return result
def write_vips_data_to_file(self, vips_data, serializer=None,
file_path=None):
"""Write VIP data to the given path.
:param vips_data: vip data
:type vips_data: list of dict
:param serializer: serializer
:param file_path: path
:type file_path: str
:return: path to resulting file
:rtype: str
"""
serializer = serializer or self.serializer
if file_path:
return serializer.write_to_full_path(
file_path,
vips_data
)
else:
return serializer.write_to_path(
self.get_default_vips_data_path(),
vips_data
)
def read_vips_data_from_file(self, file_path=None, serializer=None):
"""Read VIPs data from given path.
:param file_path: path
:type file_path: str
:param serializer: serializer object
:type serializer: object
:return: data
:rtype: list|object
"""
self._check_file_path(file_path)
return (serializer or self.serializer).read_from_file(file_path)
def set_vips_data(self, data):
"""Sending VIPs data to the Nailgun API.
:param data: VIPs data
:type data: list of dict
:return: request result
:rtype: object
"""
return self.connection.put_request(self._get_ip_addrs_url(), data)
def create_vip(self, **vip_kwargs):
"""Create VIP through request to Nailgun API
:param vip_data: attributes of the VIP to be created
"""
return self.connection.post_request(self._get_ip_addrs_url(),
vip_kwargs)