python-senlinclient-2.0.1/0000775000175000017500000000000013643337460015553 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/setup.cfg0000664000175000017500000001141513643337460017376 0ustar zuulzuul00000000000000[metadata] name = python-senlinclient summary = OpenStack Clustering API Client Library description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-senlinclient/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [files] packages = senlinclient [entry_points] openstack.cli.extension = clustering = senlinclient.plugin openstack.clustering.v1 = cluster_action_list = senlinclient.v1.action:ListAction cluster_action_show = senlinclient.v1.action:ShowAction cluster_action_update = senlinclient.v1.action:UpdateAction cluster_build_info = senlinclient.v1.build_info:BuildInfo cluster_check = senlinclient.v1.cluster:CheckCluster cluster_create = senlinclient.v1.cluster:CreateCluster cluster_delete = senlinclient.v1.cluster:DeleteCluster cluster_event_list = senlinclient.v1.event:ListEvent cluster_event_show = senlinclient.v1.event:ShowEvent cluster_list = senlinclient.v1.cluster:ListCluster cluster_members_list = senlinclient.v1.cluster:ClusterNodeList cluster_members_add = senlinclient.v1.cluster:ClusterNodeAdd cluster_members_del = senlinclient.v1.cluster:ClusterNodeDel cluster_members_replace = senlinclient.v1.cluster:ClusterNodeReplace cluster_node_adopt = senlinclient.v1.node:AdoptNode cluster_node_check = senlinclient.v1.node:CheckNode cluster_node_create = senlinclient.v1.node:CreateNode cluster_node_delete = senlinclient.v1.node:DeleteNode cluster_node_list = senlinclient.v1.node:ListNode cluster_node_recover = senlinclient.v1.node:RecoverNode cluster_node_show = senlinclient.v1.node:ShowNode cluster_node_update = senlinclient.v1.node:UpdateNode cluster_node_op = senlinclient.v1.node:NodeOp cluster_policy_attach = senlinclient.v1.cluster:ClusterPolicyAttach cluster_policy_binding_list = senlinclient.v1.cluster_policy:ClusterPolicyList cluster_policy_binding_show = senlinclient.v1.cluster_policy:ClusterPolicyShow cluster_policy_binding_update = senlinclient.v1.cluster_policy:ClusterPolicyUpdate cluster_policy_create = senlinclient.v1.policy:CreatePolicy cluster_policy_delete = senlinclient.v1.policy:DeletePolicy cluster_policy_detach = senlinclient.v1.cluster:ClusterPolicyDetach cluster_policy_list = senlinclient.v1.policy:ListPolicy cluster_policy_show = senlinclient.v1.policy:ShowPolicy cluster_policy_validate = senlinclient.v1.policy:ValidatePolicy cluster_policy_type_list = senlinclient.v1.policy_type:PolicyTypeList cluster_policy_type_show = senlinclient.v1.policy_type:PolicyTypeShow cluster_policy_update = senlinclient.v1.policy:UpdatePolicy cluster_profile_create = senlinclient.v1.profile:CreateProfile cluster_profile_delete = senlinclient.v1.profile:DeleteProfile cluster_profile_list = senlinclient.v1.profile:ListProfile cluster_profile_show = senlinclient.v1.profile:ShowProfile cluster_profile_type_ops = senlinclient.v1.profile_type:ProfileTypeOperations cluster_profile_type_list = senlinclient.v1.profile_type:ProfileTypeList cluster_profile_type_show = senlinclient.v1.profile_type:ProfileTypeShow cluster_profile_update = senlinclient.v1.profile:UpdateProfile cluster_profile_validate = senlinclient.v1.profile:ValidateProfile cluster_receiver_create = senlinclient.v1.receiver:CreateReceiver cluster_receiver_update = senlinclient.v1.receiver:UpdateReceiver cluster_receiver_delete = senlinclient.v1.receiver:DeleteReceiver cluster_receiver_list = senlinclient.v1.receiver:ListReceiver cluster_receiver_show = senlinclient.v1.receiver:ShowReceiver cluster_recover = senlinclient.v1.cluster:RecoverCluster cluster_resize = senlinclient.v1.cluster:ResizeCluster cluster_shrink = senlinclient.v1.cluster:ScaleInCluster cluster_expand = senlinclient.v1.cluster:ScaleOutCluster cluster_show = senlinclient.v1.cluster:ShowCluster cluster_update = senlinclient.v1.cluster:UpdateCluster cluster_collect = senlinclient.v1.cluster:ClusterCollect cluster_run = senlinclient.v1.cluster:ClusterRun cluster_service_list = senlinclient.v1.service:ListService cluster_op = senlinclient.v1.cluster:ClusterOp [global] setup-hooks = pbr.hooks.setup_hook [wheel] universal = 1 [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = senlinclient/locale/senlinclient.pot [compile_catalog] directory = senlinclient/locale domain = senlinclient [update_catalog] domain = senlinclient output_dir = senlinclient/locale input_file = senlinclient/locale/senlinclient.pot [egg_info] tag_build = tag_date = 0 python-senlinclient-2.0.1/senlinclient/0000775000175000017500000000000013643337460020242 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/__init__.py0000664000175000017500000000120613643337345022354 0ustar zuulzuul00000000000000# 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. import pbr.version __version__ = pbr.version.VersionInfo('python-senlinclient').version_string() python-senlinclient-2.0.1/senlinclient/plugin.py0000664000175000017500000001020513643337345022112 0ustar zuulzuul00000000000000# 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. """OpenStackClient plugin for Clustering service.""" import logging from openstack.config import cloud_region from openstack.config import defaults as config_defaults from openstack import connection from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_CLUSTERING_API_VERSION = '1' API_VERSION_OPTION = 'os_clustering_api_version' API_NAME = 'clustering' CURRENT_API_VERSION = '1.14' def _make_key(service_type, key): if not service_type: return key else: service_type = service_type.lower().replace('-', '_') return "_".join([service_type, key]) def _get_config_from_profile(profile, **kwargs): # Deal with clients still trying to use legacy profile objects region_name = None for service in profile.get_services(): if service.region: region_name = service.region service_type = service.service_type if service.interface: key = _make_key(service_type, 'interface') kwargs[key] = service.interface if service.version: version = service.version if version.startswith('v'): version = version[1:] key = _make_key(service_type, 'api_version') kwargs[key] = version if service.api_version: version = service.api_version key = _make_key(service_type, 'default_microversion') kwargs[key] = version config_kwargs = config_defaults.get_defaults() config_kwargs.update(kwargs) config = cloud_region.CloudRegion( region_name=region_name, config=config_kwargs) return config def create_connection(prof=None, cloud_region=None, **kwargs): version_key = _make_key(API_NAME, 'api_version') kwargs[version_key] = CURRENT_API_VERSION if not cloud_region: if prof: cloud_region = _get_config_from_profile(prof, **kwargs) else: # If we got the CloudRegion from python-openstackclient and it doesn't # already have a default microversion set, set it here. microversion_key = _make_key(API_NAME, 'default_microversion') cloud_region.config.setdefault(microversion_key, CURRENT_API_VERSION) user_agent = kwargs.pop('user_agent', None) app_name = kwargs.pop('app_name', None) app_version = kwargs.pop('app_version', None) if user_agent is not None and (not app_name and not app_version): app_name, app_version = user_agent.split('/', 1) return connection.Connection( config=cloud_region, app_name=app_name, app_version=app_version, **kwargs) def make_client(instance): """Returns a clustering proxy""" # TODO(mordred) the ClientManager already has an OpenStackSDK connection, # but it only has it once setup_auth has been called. For things that # don't require auth, this is problematic, so we have to make our own. # Use the CloudRegion stored on the ClientManager for now. conn = create_connection( cloud_region=instance._cli_options, ) LOG.debug('Connection: %s', conn) LOG.debug('Clustering client initialized using OpenStackSDK: %s', conn.clustering) return conn.clustering def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-clustering-api-version', metavar='', default=utils.env( 'OS_CLUSTERING_API_VERSION', default=DEFAULT_CLUSTERING_API_VERSION), help='Clustering API version, default=' + DEFAULT_CLUSTERING_API_VERSION + ' (Env: OS_CLUSTERING_API_VERSION)') return parser python-senlinclient-2.0.1/senlinclient/locale/0000775000175000017500000000000013643337460021501 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/locale/zh_CN/0000775000175000017500000000000013643337460022502 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/locale/zh_CN/LC_MESSAGES/0000775000175000017500000000000013643337460024267 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po0000664000175000017500000004472313643337345027332 0ustar zuulzuul00000000000000# zzxwill , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: python-senlinclient VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-28 14:44+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-08-20 01:56+0000\n" "Last-Translator: zzxwill \n" "Language-Team: Chinese (China)\n" "Language: zh_CN\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "" "A dictionary of parameters that will be passed to target action when the " "receiver is triggered" msgstr "字典类型的参数在receiver被触发时将传递给目标动作" msgid "" "A positive integer meaning the number of nodes to add, or a negative integer " "indicating the number of nodes to remove" msgstr "正整数意味着要添加的节点个数,或者一个负整数,表明要删除的节点个数" msgid "" "A value that is interpreted as the percentage of size adjustment. This value " "can be positive or negative" msgstr "一个值被解释为大小调整的百分比,这个值可以是正数,也可以是负数" #, python-format msgid "Action not found: %s" msgstr "Action没找到:%s" msgid "Adjustment cannot be zero." msgstr "调整值不能为0。" msgid "" "An integer specifying the number of nodes for adjustment when " "is specified" msgstr "当指定时,整数值表明节点调整的数目" msgid "Are you sure you want to delete this cluster(s) [y/N]?" msgstr "你确认你想删除这个集群吗[y/N]?" msgid "Are you sure you want to delete this node(s) [y/N]?" msgstr "你确认你想删除这个节点吗[y/N]?" msgid "Are you sure you want to delete this policy(s) [y/N]?" msgstr "你确认你想删除这个策略吗[y/N]?" msgid "Are you sure you want to delete this profile(s) [y/N]?" msgstr "你确认你想删除这个样版吗[y/N]?" msgid "Are you sure you want to delete this receiver(s) [y/N]?" msgstr "你确认你想删除这个receiver吗[y/N]?" msgid "Cluster Id or Name for this node" msgstr "该节点的集群Id或名称" msgid "Cluster capacity must be larger than or equal to zero." msgstr "集群的容量必须大于或等于0。" msgid "Cluster creation timeout in seconds" msgstr "集群创建超时时限(秒)" #, python-format msgid "Cluster not found: %s" msgstr "集群没找到:%s" msgid "" "Desired capacity of the cluster. Default to min_size if min_size is " "specified else 0." msgstr "集群期望的容量,如果min_size给定了,默认是min_size,否则是0。" #, python-format msgid "ERROR(%(code)s): %(message)s" msgstr "错误(%(code)s): %(message)s" #, python-format msgid "" "ERROR: %(message)s\n" "%(traceback)s" msgstr "" "错误: %(message)s\n" "%(traceback)s" #, python-format msgid "Event not found: %s" msgstr "事件没有找到:%s" #, python-format msgid "Failed to delete %(count)s of the %(total)s specified policy(s)." msgstr "删除%(total)s个给定的策略中的%(count)s个失败。" #, python-format msgid "Failed to delete %(count)s of the %(total)s specified profile(s)." msgstr "删除%(total)s个给定的样版中的%(count)s个失败。" #, python-format msgid "Failed to delete %(count)s of the %(total)s specified receiver(s)." msgstr "删除%(total)s个给定的receiver中的%(count)s个失败。" msgid "" "Filter parameters to apply on returned clusters. This can be specified " "multiple times, or once with parameters separated by a semicolon. The valid " "filter keys are: ['status', 'name']" msgstr "" "过滤参数以应用在返回的集群上。该操作可以指定多次,或者用分号分隔参数之后指定" "一次。有效的过滤键是['status', 'name']" msgid "" "Filter parameters to apply on returned nodes. This can be specified multiple " "times, or once with parameters separated by a semicolon. The valid filter " "keys are: ['status','name']" msgstr "" "过滤参数以应用在返回的节点上。该参数可以指定多次,也可以用分号分隔参数之后执" "行一次。有效的过滤键是['status','name']" msgid "" "Filter parameters to apply on returned policies. This can be specified " "multiple times, or once with parameters separated by a semicolon. The valid " "filter keys are: ['type', 'name']" msgstr "" "过滤参数以应用在返回的策略上。该参数可以指定多次,也可以用分号分隔参数之后执" "行一次。有效的过滤键是['type', 'name']" msgid "ID of event to display details for" msgstr "显示事件详情的ID" msgid "ID or name of cluster from which nodes are to be listed" msgstr "集群的ID或名字,该集群中的节点都将被列出来" msgid "ID or name of cluster(s) to operate on." msgstr "将要操作的集群的ID或名字。" msgid "ID or name of new profile to use" msgstr "将要使用的新样版的ID或名字" msgid "ID or name of node(s) to check." msgstr "将要检查的节点的ID或名字。" msgid "ID or name of node(s) to recover." msgstr "将要恢复的节点的ID或名字。" msgid "" "ID or name of nodes to be added; multiple nodes can be separated with \",\"" msgstr "待添加节点的ID或名称;多个节点可以用“,”分隔" msgid "ID or name of policy to be attached" msgstr "将要关联的策略的ID或名字" msgid "ID or name of policy to be detached" msgstr "将要解除关联的策略的ID或名字" msgid "ID or name of policy to be updated" msgstr "将被更新的策略的ID或名字" msgid "ID or name of the cluster to query on" msgstr "将要查询的集群的ID或名字" msgid "ID or name of the policy to query on" msgstr "将要查询的策略的ID或名字" msgid "Include physical object details" msgstr "包含物理对象的详情" msgid "" "Indicate that the cluster list should include clusters from all projects. " "This option is subject to access policy checking. Default is False" msgstr "" "这表明集群列表应该包含所有项目的集群。该选项从属于访问策略检查,默认是False" msgid "" "Indicate that the list should include policies from all projects. This " "option is subject to access policy checking. Default is False" msgstr "" "这表明列表应该包含所有项目的策略。该选项从属于访问策略检查,默认是False" msgid "" "Indicate that the list should include profiles from all projects. This " "option is subject to access policy checking. Default is False" msgstr "" "这表明列表应该包含所有项目的样版。该选项从属于访问策略检查,默认是False" msgid "" "Indicate that the list should include receivers from all projects. This " "option is subject to access policy checking. Default is False" msgstr "" "这表明列表应该包含所有项目的receiver。该选项从属于访问策略检查,默认是False" msgid "" "Indicate that this node list should include nodes from all projects. This " "option is subject to access policy checking. Default is False" msgstr "" "这表明节点列表应该包含所有项目的节点。该选项从属于访问策略检查,默认是False" msgid "Key \"error\" not exists" msgstr "键\"error\"不存在" msgid "Limit the number of actions returned" msgstr "限定action返回的个数" msgid "Limit the number of clusters returned" msgstr "限定集群返回的个数" msgid "Limit the number of events returned" msgstr "限定事件返回的个数" msgid "Limit the number of nodes returned" msgstr "限定节点返回的个数" msgid "Limit the number of policies returned" msgstr "限定策略返回的个数" msgid "Limit the number of profiles returned" msgstr "限定样版返回的个数" msgid "Limit the number of receivers returned" msgstr "限定receiver返回的个数" #, python-format msgid "Malformed exception record, missing field \"%s\"" msgstr "异常记录的格式不正确,缺少字段\"%s\"" #, python-format msgid "Malformed parameter(%s). Use the key=value format." msgstr "参数(%s)格式不正确,使用key=value格式。" msgid "Malformed parameter(status:ACTIVE). Use the key=value format." msgstr "参数(status:ACTIVE)格式不正确,使用key=value格式。" msgid "Max size cannot be less than the specified capacity." msgstr "最大值不能小于给定的容量。" msgid "Max size of the cluster. Default to -1, means unlimited" msgstr "集群容量上限。默认为-1,表示无限制" msgid "" "Metadata values to be attached to the cluster. This can be specified " "multiple times, or once with key-value pairs separated by a semicolon." msgstr "" "将要关联到集群的元数据。该元数据可以指定多次,也可以用分号分割键值对之后指定" "一次。" msgid "" "Metadata values to be attached to the node. This can be specified multiple " "times, or once with key-value pairs separated by a semicolon" msgstr "" "将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" "一次" msgid "" "Metadata values to be attached to the node. This can be specified multiple " "times, or once with key-value pairs separated by a semicolon." msgstr "" "将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" "一次。" msgid "" "Metadata values to be attached to the profile. This can be specified " "multiple times, or once with key-value pairs separated by a semicolon" msgstr "" "将要关联到样版的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" "一次" msgid "Min size cannot be larger than max size." msgstr "最小值不能大于最大值。" msgid "Min size cannot be larger than the specified capacity" msgstr "最小值不能大于给定的容量" msgid "Min size cannot be less than zero." msgstr "最小值不能小于0。" msgid "Min size of the cluster. Default to 0" msgstr "集群容量下限。默认为0" #, fuzzy msgid "Min step is only used with percentage." msgstr "最小步骤只能使用百分数。" msgid "Missing 'properties' key in spec file." msgstr "规格文件里缺少键'properties'。" msgid "Missing 'type' key in spec file." msgstr "规格文件里缺少键'type'。" msgid "Missing 'version' key in spec file." msgstr "规格文件里缺少键'version'。" msgid "Name of the cluster to create" msgstr "要创建的集群的名称" msgid "Name of the node to create" msgstr "要创建的节点的名称" msgid "Name of the policy to create" msgstr "要创建的策略的名称" msgid "Name of the profile to create" msgstr "要创建的样版的名称" msgid "Name of the receiver to create" msgstr "要创建的receiver的名称" msgid "Name or ID of cluster to be updated" msgstr "将要更新的集群的名称或ID" msgid "Name or ID of cluster to nodes from" msgstr "该集群的节点的名字或ID" msgid "Name or ID of cluster to operate on" msgstr "将要操作的集群的名字或ID" msgid "Name or ID of cluster to query on" msgstr "将要查询的集群的名字或ID" msgid "Name or ID of cluster to show" msgstr "将要显示的集群的名字或ID" msgid "Name or ID of cluster(s) to delete." msgstr "将要删除的集群的名称或ID。" msgid "Name or ID of node to update" msgstr "将要更新的节点的名字或ID" msgid "Name or ID of node(s) to delete." msgstr "待删除的节点的名称或ID。" msgid "" "Name or ID of nodes to be deleted; multiple nodes can be separated with \",\"" msgstr "待删除的节点的名称或ID;多个节点可以用“,”分隔" msgid "Name or ID of policy(s) to delete" msgstr "将要删除的策略的名称或ID" msgid "Name or ID of profile(s) to delete" msgstr "将要删除的样版的名称或ID" msgid "Name or ID of receiver(s) to delete" msgstr "要删除的receiver的名称或ID" msgid "Name or ID of the action to show the details for" msgstr "显示action详情的名字或ID" msgid "Name or ID of the node to show the details for" msgstr "显示节点详情的名字或ID。" msgid "Name or ID of the policy to be updated" msgstr "将要更新的策略的名称或ID" msgid "Name or ID of the profile to update" msgstr "将要更新的样版的名字或ID" msgid "Name or ID of the receiver to show" msgstr "待显示的receiver的名字或ID" msgid "Name or Id of the policy to show" msgstr "将要显示的策略的名字或ID" msgid "New lower bound of cluster size" msgstr "集群大小的新下限值" msgid "New name for the cluster to update" msgstr "将要更新的集群的新名称" msgid "New name for the node" msgstr "节点的新名称" msgid "New name of the policy to be updated" msgstr "将被更新的策略的新名字" msgid "New timeout (in seconds) value for the cluster" msgstr "集群新的超时时间(秒)" msgid "" "New upper bound of cluster size. A value of -1 indicates no upper limit on " "cluster size" msgstr "集群容量的上限值。默认为-1,表示集群大小无限制" msgid "No template found in the given spec file" msgstr "给定的样版文件里找不到模板" #, python-format msgid "Node not found: %s" msgstr "节点没有找到:%s" msgid "Number of nodes to be added to the specified cluster" msgstr "将要加入该集群中的节点的个数" msgid "Number of nodes to be deleted from the specified cluster" msgstr "将要从该集群中删除的节点的个数" msgid "Only one of 'capacity', 'adjustment' and 'percentage' can be specified." msgstr "只能指定'capacity', 'adjustment'和'percentage'中的一个值。" msgid "Only return clusters that appear after the given cluster ID" msgstr "仅仅返回给定集群ID后出现的集群" msgid "Only return events that appear after the given event ID" msgstr "仅仅返回给定事件ID后出现的事件" msgid "Only return nodes that appear after the given node ID" msgstr "仅仅返回给定节点ID后出现的节点" #, python-format msgid "Original error record: %s" msgstr "初始错误记录:%s" msgid "Percentage cannot be zero." msgstr "百分数不能为0。" #, python-format msgid "Policy Type not found: %s" msgstr "策略类型没找到:%s" #, python-format msgid "Policy not found: %s" msgstr "策略没找到:%s" msgid "Policy type to retrieve" msgstr "将要获取的策略类型" msgid "Print full IDs in list" msgstr "将全部ID打印到列表里" msgid "Profile Id or Name used for this node" msgstr "用于该节点的样板的Id或名称" #, python-format msgid "Profile Type not found: %s" msgstr "找不到样版类型:%s" #, python-format msgid "Profile not found: %s" msgstr "找不到样版:%s" msgid "Profile type to retrieve" msgstr "将要获取的样版类型" #, python-format msgid "Receiver not found: %s" msgstr "Receiver没找到:%s" msgid "Role for this node in the specific cluster" msgstr "在给定集群里的该节点的角色" msgid "Role for this node in the specific cluster." msgstr "特定集群中的这个节点的角色。" msgid "Skip yes/no prompt (assume yes)" msgstr "跳过yes确认/没有提示(默认是yes)" msgid "" "Sorting option which is a string containing a list of keys separated by " "commas. Each key can be optionally appended by a sort direction (:asc or :" "desc). The valid sort keys are: ['name', 'status', 'init_at', 'created_at', " "'updated_at']" msgstr "" "排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" "个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" "['name', 'status', 'init_at', 'created_at', 'updated_at']" msgid "" "Sorting option which is a string containing a list of keys separated by " "commas. Each key can be optionally appended by a sort direction (:asc or :" "desc). The valid sort keys are: ['name', 'target', 'action', 'created_at', " "'status']" msgstr "" "排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" "个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" "['name', 'target', 'action', 'created_at', 'status']" msgid "" "Sorting option which is a string containing a list of keys separated by " "commas. Each key can be optionally appended by a sort direction (:asc or :" "desc). The valid sort keys are: ['name', 'type', 'action', 'cluster_id', " "'created_at']" msgstr "" "排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" "个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" "['name', 'type', 'action', 'cluster_id', 'created_at']" msgid "" "Sorting option which is a string containing a list of keys separated by " "commas. Each key can be optionally appended by a sort direction (:asc or :" "desc). The valid sort keys are: ['type', 'name', 'created_at', 'updated_at']" msgstr "" "排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" "个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" "['type', 'name', 'created_at', 'updated_at']" msgid "" "Sorting option which is a string containing a list of keys separated by " "commas. Each key can be optionally appended by a sort direction (:asc or :" "desc). The valid sort_keys are:['type', 'name', 'created_at', 'updated_at']" msgstr "" "排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" "个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是['type', " "'name', 'created_at', 'updated_at']" msgid "The desired number of nodes of the cluster" msgstr "该集群中期望的节点个数" msgid "The new name for the profile" msgstr "该样版的新名字" msgid "The spec file used to create the policy" msgstr "用于创建策略的规格文件" msgid "The spec file used to create the profile" msgstr "用于创建样版的规格文件" #, python-format msgid "The specified file is not a valid YAML file: %s" msgstr "所提供的文件不是合法的YAML文件:%s" #, python-format msgid "Unknown exception: %s" msgstr "未知异常:%s" msgid "" "Whether events from all projects should be listed. Default to False. " "Setting this to True may demand for an admin privilege" msgstr "" "是否所有项目的事件都应该列出来,默认是False。设置该属性为True可能需要admin权" "限" msgid "Whether the policy should be enabled" msgstr "该策略是否会使其生效" msgid "Whether the policy should be enabled once attached. Default to True" msgstr "策略一旦关联是否应该设置为有效,默认是True" python-senlinclient-2.0.1/senlinclient/tests/0000775000175000017500000000000013643337460021404 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/__init__.py0000664000175000017500000000000013643337345023505 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/0000775000175000017500000000000013643337460023546 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/__init__.py0000664000175000017500000000000013643337345025647 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/policies/0000775000175000017500000000000013643337460025355 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/policies/deletion_policy.yaml0000664000175000017500000000121513643337345031424 0ustar zuulzuul00000000000000# Sample deletion policy that can be attached to a cluster. type: senlin.policy.deletion version: 1.0 description: A policy for choosing victim node(s) from a cluster for deletion. properties: # The valid values include: # OLDEST_FIRST, OLDEST_PROFILE_FIRST, YOUNGEST_FIRST, RANDOM criteria: OLDEST_FIRST # Whether deleted node should be destroyed destroy_after_deletion: True # Length in number of seconds before the actual deletion happens # This param buys an instance some time before deletion grace_period: 60 # Whether the deletion will reduce the desired capacity of # the cluster as well reduce_desired_capacity: False python-senlinclient-2.0.1/senlinclient/tests/functional/test_nodes.py0000664000175000017500000001121613643337345026272 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class NodeTest(base.OpenStackClientTestBase): """Test for nodes""" def test_node_list(self): result = self.openstack('cluster node list') node_list = self.parser.listing(result) self.assertTableStruct(node_list, ['id', 'name', 'index', 'status', 'cluster_id', 'physical_id', 'profile_name', 'created_at', 'updated_at', 'tainted']) def test_node_create(self): name = self.name_generate() pf = self.profile_create(name) node = self.node_create(pf['id'], name) self.assertEqual(node['name'], name) self.node_delete(node['id']) self.addCleanup(self.profile_delete, pf['id']) def test_node_update(self): old_name = self.name_generate() pf = self.profile_create(old_name) n1 = self.node_create(pf['id'], old_name) new_name = self.name_generate() pf_new = self.profile_create(new_name) role = 'master' tainted = 'True' cmd = ('cluster node update --name %s --role %s --profile %s ' '--tainted %s %s' % (new_name, role, pf_new['id'], tainted, n1['id'])) self.openstack(cmd) self.wait_for_status(n1['id'], 'ACTIVE', 'node', 120) raw_node = self.openstack('cluster node show %s' % n1['id']) node_data = self.show_to_dict(raw_node) self.assertEqual(node_data['name'], new_name) self.assertNotEqual(node_data['name'], old_name) self.assertEqual(node_data['role'], role) self.assertEqual(node_data['tainted'], tainted) self.assertEqual(node_data['profile_id'], pf_new['id']) self.node_delete(new_name) self.addCleanup(self.profile_delete, pf['id']) self.addCleanup(self.profile_delete, pf_new['id']) def test_node_detail(self): name = self.name_generate() pf = self.profile_create(name) node = self.node_create(pf['id'], name) cmd = ('cluster node show --details %s' % name) raw_node = self.openstack(cmd) node_data = self.show_to_dict(raw_node) self.assertIn('details', node_data) self.assertIsNotNone(node_data['details']) self.node_delete(node['id']) self.addCleanup(self.profile_delete, pf['id']) # NOTE(Qiming): Since functional tests only focus on the client/server # interaction without invovling other OpenStack services, it is not # possible to mock a node failure and then test if the check logic works. # Such tests would be left to integration tests instead. def test_node_check(self): name = self.name_generate() pf = self.profile_create(name) node = self.node_create(pf['id'], name) cmd = ('cluster node check %s' % node['id']) self.openstack(cmd) check_raw = self.openstack('cluster node show %s' % name) check_data = self.show_to_dict(check_raw) self.assertIn('Check', check_data['status_reason']) node_status = ['ACTIVE', 'ERROR'] self.assertIn(check_data['status'], node_status) self.node_delete(node['id']) self.addCleanup(self.profile_delete, pf['id']) # NOTE(Qiming): A end-to-end test of the node recover operation needs to # be done with other OpenStack services involved, thus out of scope for # functional tests. Such tests would be left to integration tests instead. def test_node_recover(self): name = self.name_generate() pf = self.profile_create(name) node = self.node_create(pf['id'], name) cmd = ('cluster node recover --check true %s' % node['id']) self.openstack(cmd) self.wait_for_status(name, 'ACTIVE', 'node', 120) recover_raw = self.openstack('cluster node show %s' % name) recover_data = self.show_to_dict(recover_raw) self.assertIn('Recover', recover_data['status_reason']) self.assertEqual('ACTIVE', recover_data['status']) self.node_delete(node['id']) self.addCleanup(self.profile_delete, pf['id']) python-senlinclient-2.0.1/senlinclient/tests/functional/test_readonly_senlin.py0000664000175000017500000000160713643337345030352 0ustar zuulzuul00000000000000# 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 tempest.lib import exceptions from senlinclient.tests.functional import base class FakeTest(base.OpenStackClientTestBase): """Test if fake actions can be detected""" def test_fake_action(self): self.assertRaises(exceptions.CommandFailed, self.openstack, 'this-does-not-exist') python-senlinclient-2.0.1/senlinclient/tests/functional/test_policies.py0000664000175000017500000000321213643337345026766 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class PolicyTest(base.OpenStackClientTestBase): """Test for policies""" def test_policy_list(self): result = self.openstack('cluster policy list') policy_list = self.parser.listing(result) self.assertTableStruct(policy_list, ['id', 'name', 'type', 'created_at']) def test_policy_create(self): name = self.name_generate() result = self.policy_create(name, 'deletion_policy.yaml') self.assertEqual(result['name'], name) self.addCleanup(self.policy_delete, result['id']) def test_policy_update(self): old_name = self.name_generate() pc1 = self.policy_create(old_name, 'deletion_policy.yaml') new_name = self.name_generate() cmd = ('cluster policy update --name %s %s' % (new_name, pc1['id'])) result = self.openstack(cmd) pc2 = self.show_to_dict(result) self.assertEqual(pc2['name'], new_name) self.assertNotEqual(pc1['name'], pc2['name']) self.addCleanup(self.policy_delete, pc2['id']) python-senlinclient-2.0.1/senlinclient/tests/functional/test_profiles.py0000664000175000017500000000322013643337345027001 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ProfileTest(base.OpenStackClientTestBase): """Test for profiles""" def test_profile_list(self): result = self.openstack('cluster profile list') profile_list = self.parser.listing(result) self.assertTableStruct(profile_list, ['id', 'name', 'type', 'created_at']) def test_pofile_create(self): name = self.name_generate() result = self.profile_create(name, 'cirros_basic.yaml') self.assertEqual(result['name'], name) self.addCleanup(self.profile_delete, result['id']) def test_profile_update(self): old_name = self.name_generate() pf1 = self.profile_create(old_name, 'cirros_basic.yaml') new_name = self.name_generate() cmd = ('cluster profile update --name %s %s' % (new_name, pf1['id'])) result = self.openstack(cmd) pf2 = self.show_to_dict(result) self.assertEqual(pf2['name'], new_name) self.assertNotEqual(pf1['name'], pf2['name']) self.addCleanup(self.profile_delete, pf2['id']) python-senlinclient-2.0.1/senlinclient/tests/functional/hooks/0000775000175000017500000000000013643337460024671 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/hooks/post_test_hook.sh0000775000175000017500000000174413643337345030304 0ustar zuulzuul00000000000000#!/bin/bash -xe # 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. # This script is executed inside post_test_hook function in devstack gate. export SENLINCLIENT_DIR="$BASE/new/python-senlinclient" source $BASE/new/devstack/openrc admin admin cd $SENLINCLIENT_DIR sudo -E chown -R $USER:stack $SENLINCLIENT_DIR # Run tests echo "Running senlinclient functional test." set +e sudo -E -H -u $USER tox -efunctional RESULT=$? set -e echo "Completed running senlinclient functional test." exit $RESULT python-senlinclient-2.0.1/senlinclient/tests/functional/hooks/pre_test_hook.sh0000775000175000017500000000263713643337345030107 0ustar zuulzuul00000000000000#!/bin/bash -xe # # 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. # This script is executed inside pre_test_hook function in devstack gate. export localconf=$BASE/new/devstack/local.conf export SENLIN_CONF=/etc/senlin/senlin.conf export SENLIN_BACKEND=${SENLIN_BACKEND:-'openstack_test'} _LOG_CFG='default_log_levels =' _LOG_CFG+='amqp=WARN,amqplib=WARN,sqlalchemy=WARN,oslo_messaging=WARN' _LOG_CFG+=',iso8601=WARN,requests.packages.urllib3.connectionpool=WARN' _LOG_CFG+=',urllib3.connectionpool=WARN' _LOG_CFG+=',requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN' _LOG_CFG+=',keystonemiddleware=WARN' _LOG_CFG+=',routes.middleware=WARN' _LOG_CFG+=',stevedore=WARN' _LOG_CFG+=',oslo_messaging._drivers.amqp=WARN' _LOG_CFG+=',oslo_messaging._drivers.amqpdriver=WARN' echo -e '[[post-config|$SENLIN_CONF]]\n[DEFAULT]\n' >> $localconf echo -e "cloud_backend=$SENLIN_BACKEND\n" >> $localconf echo -e $_LOG_CFG >> $localconf python-senlinclient-2.0.1/senlinclient/tests/functional/test_receivers.py0000664000175000017500000000516513643337345027157 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ReceiverTest(base.OpenStackClientTestBase): """Test for receivers""" def test_receiver_list(self): result = self.openstack('cluster receiver list') receiver_list = self.parser.listing(result) self.assertTableStruct(receiver_list, ['id', 'name', 'type', 'cluster_id', 'action', 'created_at']) def test_receiver_create(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) receiver = self.receiver_create(name, cluster['id']) self.addCleanup(self.receiver_delete, receiver['id']) self.assertEqual(receiver['name'], name) self.assertEqual(receiver['type'], 'webhook') self.assertEqual(receiver['action'], 'CLUSTER_SCALE_OUT') def test_receiver_update(self): old_name = self.name_generate() pf = self.profile_create(old_name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], old_name) self.addCleanup(self.cluster_delete, cluster['id']) receiver = self.receiver_create(old_name, cluster['id']) self.addCleanup(self.receiver_delete, receiver['id']) new_name = self.name_generate() cmd = ('cluster receiver update --name %s --params count=2 ' '--action CLUSTER_SCALE_IN %s' % (new_name, receiver['id'])) self.openstack(cmd) receiver_raw = self.openstack('cluster receiver show %s' % receiver['id']) receiver_data = self.show_to_dict(receiver_raw) self.assertNotEqual(receiver['name'], receiver_data['name']) self.assertEqual(receiver_data['name'], new_name) self.assertNotEqual(receiver['action'], receiver_data['action']) self.assertEqual(receiver_data['action'], 'CLUSTER_SCALE_IN') python-senlinclient-2.0.1/senlinclient/tests/functional/test_profile_types.py0000664000175000017500000000317213643337345030050 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ProfileTypeTest(base.OpenStackClientTestBase): """Test for profile types Basic smoke test for the Openstack CLI commands which do not require creating or modifying. """ def test_profile_type_list(self): result = self.openstack('cluster profile type list') profile_type = self.parser.listing(result) columns = ['name', 'version'] if any('support_status' in i for i in profile_type): columns.append('support_status') self.assertTableStruct(profile_type, columns) def test_profile_list_debug(self): self.openstack('cluster profile type list', flags='--debug') def test_profile_type_show(self): params = ['container.dockerinc.docker-1.0', 'os.heat.stack-1.0', 'os.nova.server-1.0'] for param in params: cmd = 'cluster profile type show %s' % param self.openstack(cmd) def test_profile_type_show_json(self): self.openstack('cluster profile type show os.nova.server-1.0 -f json') python-senlinclient-2.0.1/senlinclient/tests/functional/base.py0000664000175000017500000001500413643337345025034 0ustar zuulzuul00000000000000# 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. import os import six import time from oslo_utils import uuidutils from tempest.lib.cli import base from tempest.lib.cli import output_parser from tempest.lib import exceptions as tempest_lib_exc class OpenStackClientTestBase(base.ClientTestBase): """Command line client base functions.""" def setUp(self): super(OpenStackClientTestBase, self).setUp() self.parser = output_parser def _get_clients(self): cli_dir = os.environ.get( 'OS_SENLINCLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin')) return base.CLIClient( username=os.environ.get('OS_USERNAME'), password=os.environ.get('OS_PASSWORD'), tenant_name=os.environ.get('OS_TENANT_NAME'), uri=os.environ.get('OS_AUTH_URL'), cli_dir=cli_dir) def openstack(self, *args, **kwargs): return self.clients.openstack(*args, **kwargs) def show_to_dict(self, output): obj = {} items = self.parser.listing(output) for item in items: obj[item['Field']] = six.text_type(item['Value']) return dict((self._key_name(k), v) for k, v in obj.items()) def _key_name(self, key): return key.lower().replace(' ', '_') def name_generate(self): """Generate randomized name for some entity.""" name = uuidutils.generate_uuid()[:8] return name def _get_profile_path(self, profile_name): return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'profiles/%s' % profile_name) def _get_policy_path(self, policy_name): return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'policies/%s' % policy_name) def wait_for_status(self, name, status, check_type, timeout=60, poll_interval=5): """Wait until name reaches given status. :param name: node or cluster name :param status: expected status of node or cluster :param timeout: timeout in seconds :param poll_interval: poll interval in seconds """ if check_type == 'node': cmd = ('cluster node show %s' % name) elif check_type == 'cluster': cmd = ('cluster show %s' % name) time.sleep(poll_interval) start_time = time.time() while time.time() - start_time < timeout: check_status = self.openstack(cmd) result = self.show_to_dict(check_status) if result['status'] == status: break time.sleep(poll_interval) else: message = ("%s %s did not reach status %s after %d s" % (check_type, name, status, timeout)) raise tempest_lib_exc.TimeoutException(message) def wait_for_delete(self, name, check_type, timeout=60, poll_interval=5): """Wait until delete finish""" if check_type == 'node': cmd = ('cluster node show %s' % name) if check_type == 'cluster': cmd = ('cluster show %s' % name) time.sleep(poll_interval) start_time = time.time() while time.time() - start_time < timeout: try: self.openstack(cmd) except tempest_lib_exc.CommandFailed as ex: if "No Node found" or "No Cluster found" in ex.stderr: break time.sleep(poll_interval) else: message = ("failed in deleting %s %s after %d seconds" % (check_type, name, timeout)) raise tempest_lib_exc.TimeoutException(message) def policy_create(self, name, policy='deletion_policy.yaml'): pf = self._get_policy_path(policy) cmd = ('cluster policy create --spec-file %s %s' % (pf, name)) policy_raw = self.openstack(cmd) result = self.show_to_dict(policy_raw) return result def policy_delete(self, name_or_id): cmd = ('cluster policy delete %s --force' % name_or_id) self.openstack(cmd) def profile_create(self, name, profile='cirros_basic.yaml'): pf = self._get_profile_path(profile) cmd = ('cluster profile create --spec-file %s %s' % (pf, name)) profile_raw = self.openstack(cmd) result = self.show_to_dict(profile_raw) return result def profile_delete(self, name_or_id): cmd = ('cluster profile delete %s --force' % name_or_id) self.openstack(cmd) def node_create(self, profile, name): cmd = ('cluster node create --profile %s %s' % (profile, name)) node_raw = self.openstack(cmd) result = self.show_to_dict(node_raw) self.wait_for_status(name, 'ACTIVE', 'node', 120) return result def node_delete(self, name_or_id): cmd = ('cluster node delete %s --force' % name_or_id) self.openstack(cmd) self.wait_for_delete(name_or_id, 'node', 120) def cluster_create(self, profile, name, desired_capacity=0): cmd = ('cluster create --profile %s --desired-capacity %d %s' % (profile, desired_capacity, name)) cluster_raw = self.openstack(cmd) result = self.show_to_dict(cluster_raw) self.wait_for_status(name, 'ACTIVE', 'cluster', 120) return result def cluster_delete(self, name_or_id): cmd = ('cluster delete %s --force' % name_or_id) self.openstack(cmd) self.wait_for_delete(name_or_id, 'cluster', 120) def receiver_create(self, name, cluster, action='CLUSTER_SCALE_OUT', rt='webhook'): cmd = ('cluster receiver create --cluster %s --action %s --type %s ' '%s' % (cluster, action, rt, name)) receiver_raw = self.openstack(cmd) result = self.show_to_dict(receiver_raw) return result def receiver_delete(self, name_or_id): cmd = ('cluster receiver delete %s --force' % name_or_id) self.openstack(cmd) python-senlinclient-2.0.1/senlinclient/tests/functional/profiles/0000775000175000017500000000000013643337460025371 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/functional/profiles/cirros_basic.yaml0000664000175000017500000000035313643337345030722 0ustar zuulzuul00000000000000type: os.nova.server version: 1.0 properties: flavor: 1 image: "cirros-0.4.0-x86_64-disk" networks: - network: private metadata: test_key: test_value user_data: | #!/bin/sh echo 'hello, world' > /tmp/test_file python-senlinclient-2.0.1/senlinclient/tests/functional/test_actions.py0000664000175000017500000000204013643337345026615 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ActionTest(base.OpenStackClientTestBase): """Test for actions""" def test_action_list(self): result = self.openstack('cluster action list') action_list = self.parser.listing(result) self.assertTableStruct(action_list, ['id', 'name', 'action', 'status', 'target_id', 'depends_on', 'cluster_id', 'depended_by', 'created_at']) python-senlinclient-2.0.1/senlinclient/tests/functional/test_cluster_policy.py0000664000175000017500000001165313643337345030227 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ClusterPolicyTest(base.OpenStackClientTestBase): """Test cluster for policy""" def test_cluster_policy_attach_and_detach(self): name = self.name_generate() po = self.policy_create(name) self.addCleanup(self.policy_delete, po['id']) pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cp_raw = self.openstack('cluster policy binding list %s' % cluster['id']) cp_data = self.show_to_dict(cp_raw) self.assertEqual({}, cp_data) # Attach policy to cluster cmd = ('cluster policy attach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) cmd = ('cluster policy binding show --policy %s %s' % (po['id'], cluster['id'])) policy_raw = self.openstack(cmd) policy_data = self.show_to_dict(policy_raw) self.assertEqual(po['name'], policy_data['policy_name']) self.assertEqual(cluster['name'], policy_data['cluster_name']) self.assertTrue(policy_data['is_enabled']) # Detach policy from cluster cmd = ('cluster policy detach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) cp_raw = self.openstack('cluster policy binding list %s' % cluster['id']) cp_data = self.show_to_dict(cp_raw) self.assertEqual({}, cp_data) def test_cluster_policy_list(self): name = self.name_generate() po = self.policy_create(name) self.addCleanup(self.policy_delete, po['id']) pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cmd = ('cluster policy attach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) # List cluster policy binding cmd = ('cluster policy binding list --filters policy_name=%s %s' % (po['name'], cluster['id'])) result = self.openstack(cmd) binding_list = self.parser.listing(result) self.assertTableStruct(binding_list, ['policy_id', 'policy_name', 'policy_type', 'is_enabled']) cmd = ('cluster policy detach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) def test_cluster_policy_update(self): name = self.name_generate() po = self.policy_create(name) self.addCleanup(self.policy_delete, po['id']) pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cmd = ('cluster policy attach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) # Update cluster policy binding cmd = ('cluster policy binding update --policy %s --enabled false %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) cp_update = self.openstack('cluster policy binding show --policy %s %s' % (po['id'], cluster['id'])) cp_update_data = self.show_to_dict(cp_update) self.assertFalse(cp_update_data['is_enabled'].isupper()) cmd = ('cluster policy detach --policy %s %s' % (po['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) python-senlinclient-2.0.1/senlinclient/tests/functional/test_clusters.py0000664000175000017500000003174413643337345027036 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class ClusterTest(base.OpenStackClientTestBase): """Test for clusters""" def test_cluster_list(self): result = self.openstack('cluster list') cluster_list = self.parser.listing(result) self.assertTableStruct(cluster_list, ['id', 'name', 'status', 'created_at', 'updated_at']) def test_cluster_list_filters(self): params = {'name': 'test1', 'status': 'ACTIVE'} for k, v in params.items(): cmd = ('cluster list --filters %s=%s' % (k, v)) self.openstack(cmd) def test_cluster_list_sort(self): params = ['name', 'status', 'init_at', 'created_at', 'updated_at'] for param in params: cmd = 'cluster list --sort %s' % param self.openstack(cmd) def test_cluster_full_id(self): self.openstack('cluster list --full-id') def test_cluster_limit(self): self.openstack('cluster list --limit 1') def test_cluster_create(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name, 1) self.addCleanup(self.cluster_delete, cluster['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) self.assertEqual(cluster_data['name'], name) self.assertEqual(cluster_data['status'], 'ACTIVE') self.assertEqual(cluster_data['desired_capacity'], '1') def test_cluster_update(self): old_name = self.name_generate() old_pf = self.profile_create(old_name) self.addCleanup(self.profile_delete, old_pf['id']) new_name = self.name_generate() new_pf = self.profile_create(new_name) self.addCleanup(self.profile_delete, new_pf['id']) cluster = self.cluster_create(old_pf['id'], old_name, 1) self.addCleanup(self.cluster_delete, cluster['id']) self.assertEqual(cluster['name'], old_name) # cluster update cmd = ('cluster update --name %s --profile %s --timeout 300 %s' % (old_name, new_pf['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) cluster_raw = self.openstack('cluster show %s' % cluster['id']) cluster_data = self.show_to_dict(cluster_raw) node_raw = self.openstack('cluster node show %s' % cluster_data['node_ids']) node_data = self.show_to_dict(node_raw) # if not profile-only, change all profile self.assertEqual(cluster['name'], cluster_data['name']) self.assertEqual(cluster_data['profile_id'], new_pf['id']) self.assertEqual(cluster_data['timeout'], '300') self.assertEqual(new_pf['name'], node_data['profile_name']) def test_cluster_update_profile_only(self): old_name = self.name_generate() old_pf = self.profile_create(old_name) self.addCleanup(self.profile_delete, old_pf['id']) new_name = self.name_generate() new_pf = self.profile_create(new_name) self.addCleanup(self.profile_delete, new_pf['id']) cluster = self.cluster_create(old_pf['id'], old_name, 1) self.addCleanup(self.cluster_delete, cluster['id']) self.assertEqual(cluster['name'], old_name) cmd = ('cluster update --name %s --profile %s --profile-only true' ' --timeout 300 %s' % (new_name, new_pf['id'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) cluster_raw = self.openstack('cluster show %s' % cluster['id']) cluster_data = self.show_to_dict(cluster_raw) node_raw = self.openstack('cluster node show %s' % cluster_data['node_ids']) node_data = self.show_to_dict(node_raw) # if profile-only true, not change exist node profile self.assertNotEqual(cluster['name'], cluster_data['name']) self.assertNotEqual(cluster_data['profile_id'], cluster['profile_id']) self.assertEqual(cluster_data['profile_id'], new_pf['id']) self.assertEqual(cluster_data['timeout'], '300') self.assertNotEqual(new_name, node_data['profile_name']) def test_cluster_show(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) self.assertIn('node_ids', cluster_data) self.assertIn('timeout', cluster_data) def test_cluster_expand_and_shrink(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) # cluster expand self.openstack('cluster expand --count 1 %s' % name) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) expand_raw = self.openstack('cluster show %s' % name) expand_data = self.show_to_dict(expand_raw) self.assertNotEqual(cluster_data['desired_capacity'], expand_data['desired_capacity']) self.assertEqual(expand_data['desired_capacity'], '1') # cluster shrink self.openstack('cluster shrink --count 1 %s' % name) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) shrink_raw = self.openstack('cluster show %s' % name) shrink_data = self.show_to_dict(shrink_raw) self.assertNotEqual(shrink_data['desired_capacity'], expand_data['desired_capacity']) self.assertEqual(cluster_data['desired_capacity'], shrink_data['desired_capacity']) # NOTE(chenyb4): Since functional tests only focus on the client/server # interaction without invovling other OpenStack services, it is not # possible to mock a cluster failure and then test if the check logic # works. Such tests would be left to integration tests instead. def test_cluster_check(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name, 1) self.addCleanup(self.cluster_delete, cluster['id']) self.openstack('cluster check %s' % cluster['id']) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) check_raw = self.openstack('cluster show %s' % name) check_data = self.show_to_dict(check_raw) self.assertIn('CLUSTER_CHECK', check_data['status_reason']) cluster_status = ['ACTIVE', 'WARNING'] self.assertIn(check_data['status'], cluster_status) # NOTE(chenyb4): A end-to-end test of the cluster recover operation needs # to be done with other OpenStack services involved, thus out of scope # for functional tests. Such tests would be left to integration tests # instead. def test_cluster_recover(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name, 1) self.addCleanup(self.cluster_delete, cluster['id']) cmd = ('cluster recover --check true %s' % cluster['id']) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) recover_raw = self.openstack('cluster show %s' % name) recover_data = self.show_to_dict(recover_raw) self.assertIn('CLUSTER_RECOVER', recover_data['status_reason']) self.assertEqual('ACTIVE', recover_data['status']) def test_cluster_resize(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) self.assertEqual(cluster_data['desired_capacity'], '0') self.assertEqual(cluster_data['max_size'], '-1') self.assertEqual(cluster_data['min_size'], '0') cmd = ('cluster resize --max-size 5 --min-size 1 --adjustment 2 %s' % cluster['id']) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) resize_raw = self.openstack('cluster show %s' % name) resize_data = self.show_to_dict(resize_raw) self.assertEqual(resize_data['desired_capacity'], '2') self.assertEqual(resize_data['max_size'], '5') self.assertEqual(resize_data['min_size'], '1') def test_cluster_members_list(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['id']) result = self.openstack('cluster members list --full-id %s' % cluster['id']) members_list = self.parser.listing(result) self.assertTableStruct(members_list, ['id', 'name', 'index', 'status', 'physical_id', 'created_at']) def test_cluster_members_add_and_del(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name) self.addCleanup(self.cluster_delete, cluster['name']) node = self.node_create(pf['id'], name) self.addCleanup(self.node_delete, node['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) self.assertEqual('', cluster_data['node_ids']) # Add exist node to cluster cmd = ('cluster members add --nodes %s %s' % (node['name'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) mem_ad_raw = self.openstack('cluster show %s' % name) mem_ad_data = self.show_to_dict(mem_ad_raw) self.assertNotEqual('', mem_ad_data['node_ids']) self.assertIn(node['id'], mem_ad_data['node_ids']) # Delete node from cluster cmd = ('cluster members del --nodes %s %s' % (node['name'], cluster['id'])) self.openstack(cmd) self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) mem_del_raw = self.openstack('cluster show %s' % name) mem_del_data = self.show_to_dict(mem_del_raw) self.assertEqual('', mem_del_data['node_ids']) self.assertNotIn(node['id'], mem_del_data['node_ids']) def test_cluster_members_replace(self): name = self.name_generate() pf = self.profile_create(name) self.addCleanup(self.profile_delete, pf['id']) cluster = self.cluster_create(pf['id'], name, 1) self.addCleanup(self.cluster_delete, cluster['id']) cluster_raw = self.openstack('cluster show %s' % name) cluster_data = self.show_to_dict(cluster_raw) # Create replace node new_node = self.node_create(pf['id'], name) self.addCleanup(self.node_delete, new_node['id']) self.assertNotIn(new_node['id'], cluster_data['node_ids']) # Cluster node replace old_node = cluster_data['node_ids'] self.addCleanup(self.node_delete, old_node) cmd = ('cluster members replace --nodes %s=%s %s' % (old_node, new_node['id'], cluster['id'])) self.openstack(cmd, flags='--debug') self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120) replace_raw = self.openstack('cluster show %s' % name) replace_data = self.show_to_dict(replace_raw) self.assertIn(new_node['id'], replace_data['node_ids']) self.assertNotIn(old_node, replace_data['node_ids']) python-senlinclient-2.0.1/senlinclient/tests/functional/test_policy_types.py0000664000175000017500000000277113643337345027713 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class PolicyTypeTest(base.OpenStackClientTestBase): """Test for policy types""" def test_policy_type_list(self): result = self.openstack('cluster policy type list') policy_type = self.parser.listing(result) columns = ['name', 'version'] if any('support_status' in i for i in policy_type): columns.append('support_status') self.assertTableStruct(policy_type, columns) def test_policy_type_show(self): params = ['senlin.policy.affinity-1.0', 'senlin.policy.batch-1.0', 'senlin.policy.deletion-1.0', 'senlin.policy.health-1.0', 'senlin.policy.loadbalance-1.1', 'senlin.policy.region_placement-1.0', 'senlin.policy.zone_placement-1.0'] for param in params: cmd = 'cluster policy type show %s' % param self.openstack(cmd) python-senlinclient-2.0.1/senlinclient/tests/functional/test_version.py0000664000175000017500000000137013643337345026647 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class VersionTest(base.OpenStackClientTestBase): """Test for versions""" def test_openstack_version(self): self.openstack('', flags='--version') python-senlinclient-2.0.1/senlinclient/tests/functional/test_help.py0000664000175000017500000000156013643337345026113 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class HelpTest(base.OpenStackClientTestBase): """Test for help commands""" def test_help_cmd(self): help_text = self.openstack('help cluster list') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: openstack cluster list') python-senlinclient-2.0.1/senlinclient/tests/functional/test_events.py0000664000175000017500000000210713643337345026465 0ustar zuulzuul00000000000000# 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 senlinclient.tests.functional import base class EventTest(base.OpenStackClientTestBase): """Test for events""" def test_event_list(self): result = self.openstack('cluster event list') event_list = self.parser.listing(result) self.assertTableStruct(event_list, ['id', 'generated_at', 'obj_type', 'obj_id', 'obj_name', 'action', 'status', 'level', 'cluster_id', 'meta_data']) python-senlinclient-2.0.1/senlinclient/tests/test_specs/0000775000175000017500000000000013643337460023560 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/test_specs/nova_server.yaml0000664000175000017500000000016113643337345026775 0ustar zuulzuul00000000000000type: os.nova.server version: 1.0 properties: name: cirros_server flavor: 1 image: cirros-0.3.4-x86_64-uec python-senlinclient-2.0.1/senlinclient/tests/test_specs/deletion_policy.yaml0000664000175000017500000000036013643337345027627 0ustar zuulzuul00000000000000type: senlin.policy.deletion version: 1.0 description: A policy for choosing victim node(s) from a cluster for deletion. properties: criteria: OLDEST_FIRST destroy_after_deletion: True grace_period: 60 reduce_desired_capacity: Falsepython-senlinclient-2.0.1/senlinclient/tests/unit/0000775000175000017500000000000013643337460022363 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/unit/__init__.py0000664000175000017500000000000013643337345024464 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/unit/test_format_utils.py0000664000175000017500000000511213643337345026505 0ustar zuulzuul00000000000000# 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. import yaml from oslo_serialization import jsonutils from osc_lib.tests import utils from senlinclient.common import format_utils columns = ['col1', 'col2', 'col3'] data = ['abcde', ['fg', 'hi', 'jk'], {'lmnop': 'qrstu'}] class ShowJson(format_utils.JsonFormat): def take_action(self, parsed_args): return columns, data class ShowYaml(format_utils.YamlFormat): def take_action(self, parsed_args): return columns, data class ShowShell(format_utils.ShellFormat): def take_action(self, parsed_args): return columns, data class ShowValue(format_utils.ValueFormat): def take_action(self, parsed_args): return columns, data class TestFormats(utils.TestCommand): def test_json_format(self): self.cmd = ShowJson(self.app, None) parsed_args = self.check_parser(self.cmd, [], []) expected = jsonutils.dumps(dict(zip(columns, data)), indent=2) self.cmd.run(parsed_args) self.assertEqual(jsonutils.loads(expected), jsonutils.loads(self.app.stdout.make_string())) def test_yaml_format(self): self.cmd = ShowYaml(self.app, None) parsed_args = self.check_parser(self.cmd, [], []) expected = yaml.safe_dump(dict(zip(columns, data)), default_flow_style=False) self.cmd.run(parsed_args) self.assertEqual(expected, self.app.stdout.make_string()) def test_shell_format(self): self.cmd = ShowShell(self.app, None) parsed_args = self.check_parser(self.cmd, [], []) expected = '''\ col1="abcde" col2="['fg', 'hi', 'jk']" col3="{'lmnop': 'qrstu'}" ''' self.cmd.run(parsed_args) self.assertEqual(expected, self.app.stdout.make_string()) def test_value_format(self): self.cmd = ShowValue(self.app, None) parsed_args = self.check_parser(self.cmd, [], []) expected = '''\ abcde ['fg', 'hi', 'jk'] {'lmnop': 'qrstu'} ''' self.cmd.run(parsed_args) self.assertEqual(expected, self.app.stdout.make_string()) python-senlinclient-2.0.1/senlinclient/tests/unit/test_plugin.py0000664000175000017500000000466213643337345025304 0ustar zuulzuul00000000000000# 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. import mock from openstack import connection as sdk_connection import testtools from senlinclient import plugin class TestPlugin(testtools.TestCase): @mock.patch.object(sdk_connection, 'Connection') def test_create_connection_with_profile(self, mock_connection): class FakeService(object): interface = 'public' region = 'a_region' version = '1' api_version = None service_type = 'clustering' mock_prof = mock.Mock() mock_prof.get_services.return_value = [FakeService()] mock_conn = mock.Mock() mock_connection.return_value = mock_conn kwargs = { 'user_id': '123', 'password': 'abc', 'auth_url': 'test_url' } res = plugin.create_connection(mock_prof, **kwargs) mock_connection.assert_called_once_with( app_name=None, app_version=None, config=mock.ANY, clustering_api_version=plugin.CURRENT_API_VERSION, **kwargs ) self.assertEqual(mock_conn, res) @mock.patch.object(sdk_connection, 'Connection') def test_create_connection_without_profile(self, mock_connection): mock_conn = mock.Mock() mock_connection.return_value = mock_conn kwargs = { 'interface': 'public', 'region_name': 'RegionOne', 'user_id': '123', 'password': 'abc', 'auth_url': 'test_url' } res = plugin.create_connection(**kwargs) mock_connection.assert_called_once_with( app_name=None, app_version=None, auth_url='test_url', clustering_api_version=plugin.CURRENT_API_VERSION, config=None, interface='public', password='abc', region_name='RegionOne', user_id='123' ) self.assertEqual(mock_conn, res) python-senlinclient-2.0.1/senlinclient/tests/unit/fakes.py0000664000175000017500000000153413643337345024033 0ustar zuulzuul00000000000000# 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 senlinclient.common import utils def do_command_foo(sc, args): """Pydoc for command foo.""" return @utils.arg('-F', '--flag', metavar='', help='Flag desc.') @utils.arg('arg1', metavar='', help='Arg1 desc') def do_command_bar(sc, args): """This is the command doc.""" return python-senlinclient-2.0.1/senlinclient/tests/unit/test_utils.py0000664000175000017500000000710413643337345025140 0ustar zuulzuul00000000000000# 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 heatclient.common import template_utils import mock import six import testtools from senlinclient.common import exc from senlinclient.common.i18n import _ from senlinclient.common import utils class UtilTest(testtools.TestCase): def test_format_parameter(self): params = ['status=ACTIVE;name=cluster1'] format_params = {'status': 'ACTIVE', 'name': 'cluster1'} self.assertEqual(format_params, utils.format_parameters(params)) def test_format_parameter_split(self): params = ['status=ACTIVE', 'name=cluster1'] format_params = {'status': 'ACTIVE', 'name': 'cluster1'} self.assertEqual(format_params, utils.format_parameters(params)) def test_format_parameter_none_dict(self): params = ['{}'] self.assertEqual({}, utils.format_parameters(params)) def test_format_parameter_none(self): self.assertEqual({}, utils.format_parameters(None)) def test_format_parameter_bad_format(self): params = ['status:ACTIVE;name:cluster1'] ex = self.assertRaises(exc.CommandError, utils.format_parameters, params) msg = _('Malformed parameter(status:ACTIVE). ' 'Use the key=value format.') self.assertEqual(msg, six.text_type(ex)) @mock.patch.object(template_utils, 'process_multiple_environments_and_files') @mock.patch.object(template_utils, 'get_template_contents') def test_process_stack_spec(self, mock_get_temp, mock_process): spec = { 'template': 'temp.yaml', 'disable_rollback': True, 'context': { 'region_name': 'RegionOne' }, } tpl_files = {'fake_key1': 'fake_value1'} template = mock.Mock() mock_get_temp.return_value = tpl_files, template env_files = {'fake_key2': 'fake_value2'} env = mock.Mock() mock_process.return_value = env_files, env new_spec = utils.process_stack_spec(spec) stack_spec = { 'disable_rollback': True, 'context': { 'region_name': 'RegionOne', }, 'parameters': {}, 'timeout': 60, 'template': template, 'files': { 'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2', }, 'environment': env } self.assertEqual(stack_spec, new_spec) mock_get_temp.assert_called_once_with(template_file='temp.yaml') mock_process.assert_called_once_with(env_paths=None) def test_json_formatter_with_empty_json(self): params = {} self.assertEqual('{}', utils.json_formatter(params)) def test_list_formatter_with_list(self): params = ['foo', 'bar'] self.assertEqual('foo\nbar', utils.list_formatter(params)) def test_list_formatter_with_empty_list(self): params = [] self.assertEqual('', utils.list_formatter(params)) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/0000775000175000017500000000000013643337460022711 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/unit/v1/__init__.py0000664000175000017500000000000013643337345025012 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_action.py0000664000175000017500000002034413643337345025604 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import action as osc_action class TestAction(fakes.TestClusteringv1): def setUp(self): super(TestAction, self).setUp() self.mock_client = self.app.client_manager.clustering class TestActionList(TestAction): columns = ['id', 'name', 'action', 'status', 'target_id', 'depends_on', 'depended_by', 'created_at', 'cluster_id'] defaults = { 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestActionList, self).setUp() self.cmd = osc_action.ListAction(self.app, None) fake_action = mock.Mock( action="NODE_CREATE", cluster_id="FAKE_CLUSTER_ID", cause="RPC Request", created_at="2015-12-04T04:54:41", depended_by=[], depends_on=[], end_time=1425550000.0, id="2366d440-c73e-4961-9254-6d1c3af7c167", inputs={}, interval=-1, name="node_create_0df0931b", outputs={}, owner=None, start_time=1425550000.0, status="SUCCEEDED", status_reason="Action completed successfully.", target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba", timeout=3600, updated_at=None ) fake_action.to_dict = mock.Mock(return_value={}) self.mock_client.actions = mock.Mock(return_value=[fake_action]) def test_action_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_action_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_action_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_action_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_action_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.actions.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_action_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.actions.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_action_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_action' arglist = ['--filter', 'name=my_action'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_action_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.actions.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestActionShow(TestAction): def setUp(self): super(TestActionShow, self).setUp() self.cmd = osc_action.ShowAction(self.app, None) fake_action = mock.Mock( action="NODE_CREATE", cluster_id="FAKE_CLUSTER_ID", cause="RPC Request", created_at="2015-12-04T04:54:41", depended_by=[], depends_on=[], end_time=1425550000.0, id="2366d440-c73e-4961-9254-6d1c3af7c167", inputs={}, interval=-1, name="node_create_0df0931b", outputs={}, owner=None, start_time=1425550000.0, status="SUCCEEDED", status_reason="Action completed successfully.", target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba", timeout=3600, updated_at=None ) fake_action.to_dict = mock.Mock(return_value={}) self.mock_client.get_action = mock.Mock(return_value=fake_action) def test_action_show(self): arglist = ['my_action'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_action.assert_called_with('my_action') def test_action_show_not_found(self): arglist = ['my_action'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_action.side_effect = sdk_exc.ResourceNotFound() error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Action not found: my_action', str(error)) class TestActionUpdate(TestAction): def setUp(self): super(TestActionUpdate, self).setUp() self.cmd = osc_action.UpdateAction(self.app, None) fake_action = mock.Mock( action="NODE_CREATE", cluster_id="FAKE_CLUSTER_ID", cause="RPC Request", created_at="2015-12-04T04:54:41", depended_by=[], depends_on=[], end_time=1425550000.0, id="2366d440-c73e-4961-9254-6d1c3af7c167", inputs={}, interval=-1, name="node_create_0df0931b", outputs={}, owner=None, start_time=1425550000.0, status="INIT", status_reason="Action completed successfully.", target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba", timeout=3600, updated_at=None ) fake_action.to_dict = mock.Mock(return_value={}) self.mock_client.get_action = mock.Mock(return_value=fake_action) self.mock_client.update_action = mock.Mock(return_value=fake_action) def test_action_update(self): arglist = ['--status', 'CANCELLED', '2366d440-c73e-4961-9254-6d1c3af7c167'] parsed_args = self.check_parser(self.cmd, arglist, []) defaults = { "status": "CANCELLED" } self.cmd.take_action(parsed_args) self.mock_client.update_action.assert_called_with( "2366d440-c73e-4961-9254-6d1c3af7c167", **defaults) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_service.py0000664000175000017500000000320313643337345025762 0ustar zuulzuul00000000000000# 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. import mock from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import service as osc_service class TestServiceList(fakes.TestClusteringv1): columns = ['binary', 'host', 'status', 'state', 'updated_at', 'disabled_reason'] def setUp(self): super(TestServiceList, self).setUp() self.mock_client = self.app.client_manager.clustering self.cmd = osc_service.ListService(self.app, None) fake_service = mock.Mock( Binary='senlin-engine', Host='Host1', Status='enabled', State='up', Updated_at=None, Disabled_Reason=None, ) fake_service.name = 'test_service' fake_service.to_dict = mock.Mock(return_value={}) self.mock_client.services = mock.Mock(return_value=[fake_service]) def test_service(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.services.assert_called_with() self.assertEqual(self.columns, columns) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_event.py0000664000175000017500000001437013643337345025452 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import event as osc_event class TestEvent(fakes.TestClusteringv1): def setUp(self): super(TestEvent, self).setUp() self.mock_client = self.app.client_manager.clustering class TestEventList(TestEvent): columns = ['id', 'generated_at', 'obj_type', 'obj_id', 'obj_name', 'action', 'status', 'level', 'cluster_id', 'meta_data'] defaults = { 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestEventList, self).setUp() self.cmd = osc_event.ListEvent(self.app, None) fake_event = mock.Mock( action="CREATE", cluster_id=None, id="2d255b9c-8f36-41a2-a137-c0175ccc29c3", level="20", obj_id="0df0931b-e251-4f2e-8719-4ebfda3627ba", obj_name="node009", obj_type="NODE", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="CREATING", generated_at="2015-03-05T08:53:15", user_id="a21ded6060534d99840658a777c2af5a" ) fake_event.to_dict = mock.Mock(return_value={}) self.mock_client.events = mock.Mock(return_value=[fake_event]) def test_event_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_event_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_event_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_event_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_event_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.events.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_event_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.events.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_event_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_event' arglist = ['--filter', 'name=my_event'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_event_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.events.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestEventShow(TestEvent): def setUp(self): super(TestEventShow, self).setUp() self.cmd = osc_event.ShowEvent(self.app, None) fake_event = mock.Mock( action="CREATE", cluster_id=None, id="2d255b9c-8f36-41a2-a137-c0175ccc29c3", level="20", obj_id="0df0931b-e251-4f2e-8719-4ebfda3627ba", obj_name="node009", obj_type="NODE", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="CREATING", generated_at="2015-03-05T08:53:15", user_id="a21ded6060534d99840658a777c2af5a" ) fake_event.to_dict = mock.Mock(return_value={}) self.mock_client.get_event = mock.Mock(return_value=fake_event) def test_event_show(self): arglist = ['my_event'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_event.assert_called_with('my_event') def test_event_show_not_found(self): arglist = ['my_event'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_event.side_effect = sdk_exc.ResourceNotFound() error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Event not found: my_event', str(error)) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_receiver.py0000664000175000017500000003562613643337345026144 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from osc_lib import utils import six from senlinclient.common.i18n import _ from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import receiver as osc_receiver class TestReceiver(fakes.TestClusteringv1): def setUp(self): super(TestReceiver, self).setUp() self.mock_client = self.app.client_manager.clustering class TestReceiverList(TestReceiver): columns = ['id', 'name', 'type', 'cluster_id', 'action', 'created_at'] defaults = { 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestReceiverList, self).setUp() self.cmd = osc_receiver.ListReceiver(self.app, None) fake_receiver = mock.Mock( action="CLUSTER_SCALE_OUT", actor={}, channel={ "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1" "-8c6a-74ba891e5422/trigger?V=1&count=1" }, cluster_id="ae63a10b-4a90-452c-aef1-113a0b255ee3", created_at="2015-06-27T05:09:43", domain_id="Default", id="573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", name="cluster_inflate", params={"count": "1"}, project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", type="webhook", updated_at=None, user_id="b4ad2d6e18cc2b9c48049f6dbe8a5b3c" ) fake_receiver.to_dict = mock.Mock(return_value={}) self.mock_client.receivers = mock.Mock(return_value=[fake_receiver]) def test_receiver_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_receiver_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_receiver_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_receiver_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_receiver_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.receivers.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_receiver_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.receivers.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_receiver_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_receiver' arglist = ['--filter', 'name=my_receiver'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_receiver_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.receivers.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestReceiverShow(TestReceiver): def setUp(self): super(TestReceiverShow, self).setUp() self.cmd = osc_receiver.ShowReceiver(self.app, None) fake_receiver = mock.Mock( action="CLUSTER_SCALE_OUT", actor={}, channel={ "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1" "-8c6a-74ba891e5422/trigger?V=1&count=1" }, cluster_id="ae63a10b-4a90-452c-aef1-113a0b255ee3", created_at="2015-06-27T05:09:43", domain_id="Default", id="573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", name="cluster_inflate", params={"count": "1"}, project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", type="webhook", updated_at=None, user_id="b4ad2d6e18cc2b9c48049f6dbe8a5b3c" ) fake_receiver.to_dict = mock.Mock(return_value={}) self.mock_client.get_receiver = mock.Mock(return_value=fake_receiver) def test_receiver_show(self): arglist = ['my_receiver'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_receiver.assert_called_with('my_receiver') def test_receiver_show_not_found(self): arglist = ['my_receiver'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_receiver.side_effect = sdk_exc.ResourceNotFound() error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Receiver not found: my_receiver', str(error)) class TestReceiverCreate(TestReceiver): args = { "action": "CLUSTER_SCALE_OUT", "cluster_id": "my_cluster", "name": "my_receiver", "params": { "count": "1" }, "type": "webhook" } def setUp(self): super(TestReceiverCreate, self).setUp() self.cmd = osc_receiver.CreateReceiver(self.app, None) fake_receiver = mock.Mock( action="CLUSTER_SCALE_OUT", actor={}, channel={ "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1" "-8c6a-74ba891e5422/trigger?V=1&count=1" }, cluster_id="ae63a10b-4a90-452c-aef1-113a0b255ee3", created_at="2015-06-27T05:09:43", domain_id="Default", id="573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", name="cluster_inflate", params={"count": "1"}, project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", type="webhook", updated_at=None, user_id="b4ad2d6e18cc2b9c48049f6dbe8a5b3c" ) fake_receiver.to_dict = mock.Mock(return_value={}) self.mock_client.create_receiver = mock.Mock( return_value=fake_receiver) self.mock_client.get_receiver = mock.Mock( return_value=fake_receiver) def test_receiver_create_webhook(self): arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT', '--cluster', 'my_cluster', '--params', 'count=1', '--type', 'webhook'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_receiver.assert_called_with(**self.args) def test_receiver_create_webhook_failed(self): arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT', '--params', 'count=1', '--type', 'webhook'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn(_('cluster and action parameters are required to create ' 'webhook type of receiver'), str(error)) def test_receiver_create_non_webhook(self): arglist = ['my_receiver', '--params', 'count=1', '--type', 'foo'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) args = copy.deepcopy(self.args) args['type'] = 'foo' args['cluster_id'] = None args['action'] = None self.mock_client.create_receiver.assert_called_with(**args) class TestReceiverUpdate(TestReceiver): args = { "action": "CLUSTER_SCALE_OUT", "name": "test_receiver", "params": { "count": "2" }, } def setUp(self): super(TestReceiverUpdate, self).setUp() self.cmd = osc_receiver.UpdateReceiver(self.app, None) fake_receiver = mock.Mock( action="CLUSTER_SCALE_IN", actor={}, channel={ "alarm_url": "http://node1:8778/v1/webhooks/e03dd2e5-8f2e-4ec1" "-8c6a-74ba891e5422/trigger?V=1&count=1" }, created_at="2015-06-27T05:09:43", domain_id="Default", id="573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", params={"count": "1"}, project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", updated_at=None, user_id="b4ad2d6e18cc2b9c48049f6dbe8a5b3c" ) fake_receiver.name = "cluster_inflate" fake_receiver.action = "CLUSTER_SCALE_IN" fake_receiver.params = {"count": "1"} fake_receiver.to_dict = mock.Mock(return_value={}) self.mock_client.update_receiver = mock.Mock( return_value=fake_receiver) self.mock_client.get_receiver = mock.Mock(return_value=fake_receiver) self.mock_client.find_receiver = mock.Mock(return_value=fake_receiver) utils.get_dict_properties = mock.Mock(return_value='') def test_receiver_update_defaults(self): arglist = ['--name', 'test_receiver', '--action', 'CLUSTER_SCALE_OUT', '--params', 'count=2', '573aa1ba'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.update_receiver.assert_called_with( "573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", **self.args) def test_receiver_update_not_found(self): arglist = ['--name', 'test_receiver', '--action', 'CLUSTER_SCALE_OUT', '--params', 'count=2', '573aa1b2'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.find_receiver.return_value = None error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Receiver not found: 573aa1b2', str(error)) class TestReceiverDelete(TestReceiver): def setUp(self): super(TestReceiverDelete, self).setUp() self.cmd = osc_receiver.DeleteReceiver(self.app, None) self.mock_client.delete_receiver = mock.Mock() def test_receiver_delete(self): arglist = ['receiver1', 'receiver2', 'receiver3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_receiver.assert_has_calls( [mock.call('receiver1', False), mock.call('receiver2', False), mock.call('receiver3', False)] ) def test_receiver_delete_force(self): arglist = ['receiver1', 'receiver2', 'receiver3', '--force'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_receiver.assert_has_calls( [mock.call('receiver1', False), mock.call('receiver2', False), mock.call('receiver3', False)] ) def test_receiver_delete_not_found(self): arglist = ['my_receiver'] self.mock_client.delete_receiver.side_effect = ( sdk_exc.ResourceNotFound) parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Failed to delete 1 of the 1 specified receiver(s).', str(error)) def test_receiver_delete_one_found_one_not_found(self): arglist = ['receiver1', 'receiver2'] self.mock_client.delete_receiver.side_effect = ( [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.mock_client.delete_receiver.assert_has_calls( [mock.call('receiver1', False), mock.call('receiver2', False)] ) self.assertEqual('Failed to delete 1 of the 2 specified receiver(s).', str(error)) @mock.patch('sys.stdin', spec=six.StringIO) def test_receiver_delete_prompt_yes(self, mock_stdin): arglist = ['my_receiver'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'y' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_receiver.assert_called_with('my_receiver', False) @mock.patch('sys.stdin', spec=six.StringIO) def test_receiver_delete_prompt_no(self, mock_stdin): arglist = ['my_receiver'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'n' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_receiver.assert_not_called() python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_build_info.py0000664000175000017500000000243213643337345026437 0ustar zuulzuul00000000000000# 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. import mock from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import build_info as osc_build_info class TestBuildInfo(fakes.TestClusteringv1): def setUp(self): super(TestBuildInfo, self).setUp() self.cmd = osc_build_info.BuildInfo(self.app, None) self.mock_client = self.app.client_manager.clustering fake_bi = mock.Mock( api={"revision": "1.0"}, engine={"revision": "1.0"} ) self.mock_client.get_build_info = mock.Mock(return_value=fake_bi) def test_build_info(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_build_info.assert_called_with() python-senlinclient-2.0.1/senlinclient/tests/unit/v1/fakes.py0000664000175000017500000001107213643337345024357 0ustar zuulzuul00000000000000# 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. import sys import mock from osc_lib.tests import utils import requests from oslo_serialization import jsonutils AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" USERNAME = "itchy" PASSWORD = "scratchy" TEST_RESPONSE_DICT_V3 = { "token": { "audit_ids": [ "a" ], "catalog": [ ], "expires_at": "2034-09-29T18:27:15.978064Z", "extras": {}, "issued_at": "2014-09-29T17:27:15.978097Z", "methods": [ "password" ], "project": { "domain": { "id": "default", "name": "Default" }, "id": "bbb", "name": "project" }, "roles": [ ], "user": { "domain": { "id": "default", "name": "Default" }, "id": "aaa", "name": USERNAME } } } TEST_VERSIONS = { "versions": { "values": [ { "id": "v3.0", "links": [ { "href": AUTH_URL, "rel": "self" } ], "media-types": [ { "base": "application/json", "type": "application/vnd.openstack.identity-v3+json" }, { "base": "application/xml", "type": "application/vnd.openstack.identity-v3+xml" } ], "status": "stable", "updated": "2013-03-06T00:00:00Z" } ] } } class FakeStdout(object): def __init__(self): self.content = [] def write(self, text): self.content.append(text) def make_string(self): result = '' for line in self.content: result = result + line return result class FakeApp(object): def __init__(self, _stdout): self.stdout = _stdout self.client_manager = None self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr class FakeClient(object): def __init__(self, **kwargs): self.auth_url = kwargs['auth_url'] self.token = kwargs['token'] class FakeClientManager(object): def __init__(self): self.compute = None self.identity = None self.image = None self.object_store = None self.volume = None self.network = None self.session = None self.auth_ref = None class FakeModule(object): def __init__(self, name, version): self.name = name self.__version__ = version class FakeResource(object): def __init__(self, manager, info, loaded=False): self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def _add_details(self, info): for (k, v) in info.items(): setattr(self, k, v) def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) class FakeResponse(requests.Response): def __init__(self, headers={}, status_code=200, data=None, encoding=None): super(FakeResponse, self).__init__() self.status_code = status_code self.headers.update(headers) self._content = jsonutils.dump_as_bytes(data) class FakeClusteringv1Client(object): def __init__(self, **kwargs): self.http_client = mock.Mock() self.http_client.auth_token = kwargs['token'] self.profiles = FakeResource(None, {}) class TestClusteringv1(utils.TestCommand): def setUp(self): super(TestClusteringv1, self).setUp() self.app.client_manager.clustering = FakeClusteringv1Client( token=AUTH_TOKEN, auth_url=AUTH_URL ) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_cluster_policy.py0000664000175000017500000001005013643337345027360 0ustar zuulzuul00000000000000# 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. import mock from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster_policy as osc_cluster_policy class TestClusterPolicy(fakes.TestClusteringv1): def setUp(self): super(TestClusterPolicy, self).setUp() self.mock_client = self.app.client_manager.clustering class TestClusterPolicyList(TestClusterPolicy): def setUp(self): super(TestClusterPolicyList, self).setUp() self.cmd = osc_cluster_policy.ClusterPolicyList(self.app, None) fake_cluster = mock.Mock(id='C1') self.mock_client.get_cluster = mock.Mock(return_value=fake_cluster) fake_binding = mock.Mock( cluster_id="7d85f602-a948-4a30-afd4-e84f47471c15", cluster_name="my_cluster", is_enabled=True, id="06be3a1f-b238-4a96-a737-ceec5714087e", policy_id="714fe676-a08f-4196-b7af-61d52eeded15", policy_name="my_policy", policy_type="senlin.policy.deletion-1.0" ) fake_binding.to_dict = mock.Mock(return_value={}) self.mock_client.cluster_policies = mock.Mock( return_value=[fake_binding]) def test_cluster_policy_list(self): arglist = ['--sort', 'name:asc', '--filter', 'name=my_policy', 'my_cluster', '--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) expected_columns = ['policy_id', 'policy_name', 'policy_type', 'is_enabled'] columns, data = self.cmd.take_action(parsed_args) self.mock_client.get_cluster.assert_called_with('my_cluster') self.mock_client.cluster_policies.assert_called_with( 'C1', name="my_policy", sort="name:asc") self.assertEqual(expected_columns, columns) class TestClusterPolicyShow(TestClusterPolicy): def setUp(self): super(TestClusterPolicyShow, self).setUp() self.cmd = osc_cluster_policy.ClusterPolicyShow(self.app, None) fake_binding = mock.Mock( cluster_id="7d85f602-a948-4a30-afd4-e84f47471c15", cluster_name="my_cluster", is_enabled=True, id="06be3a1f-b238-4a96-a737-ceec5714087e", policy_id="714fe676-a08f-4196-b7af-61d52eeded15", policy_name="my_policy", policy_type="senlin.policy.deletion-1.0" ) fake_binding.to_dict = mock.Mock(return_value={}) self.mock_client.get_cluster_policy = mock.Mock( return_value=fake_binding) def test_cluster_policy_show(self): arglist = ['--policy', 'my_policy', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_cluster_policy.assert_called_with('my_policy', 'my_cluster') class TestClusterPolicyUpdate(TestClusterPolicy): def setUp(self): super(TestClusterPolicyUpdate, self).setUp() self.cmd = osc_cluster_policy.ClusterPolicyUpdate(self.app, None) fake_resp = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} self.mock_client.update_cluster_policy = mock.Mock( return_value=fake_resp) def test_cluster_policy_update(self): arglist = ['--policy', 'my_policy', '--enabled', 'true', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.update_cluster_policy.assert_called_with( 'my_cluster', 'my_policy', enabled=True) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_node.py0000664000175000017500000006113413643337345025256 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import node as osc_node class TestNode(fakes.TestClusteringv1): def setUp(self): super(TestNode, self).setUp() self.mock_client = self.app.client_manager.clustering class TestNodeList(TestNode): columns = ['id', 'name', 'index', 'status', 'cluster_id', 'physical_id', 'profile_name', 'created_at', 'updated_at', 'tainted'] defaults = { 'cluster_id': None, 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestNodeList, self).setUp() self.cmd = osc_node.ListNode(self.app, None) fake_node = mock.Mock( cluster_id=None, created_at="2015-02-27T04:39:21", data={}, details={}, domain=None, id="573aa1ba-bf45-49fd-907d-6b5d6e6adfd3", index=-1, init_at="2015-02-27T04:39:18", metadata={}, physical_id="cc028275-d078-4729-bf3e-154b7359814b", profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", role=None, status="ACTIVE", status_reason="Creation succeeded", tainted=True, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "node00a" fake_node.to_dict = mock.Mock(return_value={}) self.mock_client.nodes = mock.Mock(return_value=[fake_node]) def test_node_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_node_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_node_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_node_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_node_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.nodes.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_node_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.nodes.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_node_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_node' arglist = ['--filter', 'name=my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_node_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestNodeShow(TestNode): response = {"node": { }} def setUp(self): super(TestNodeShow, self).setUp() self.cmd = osc_node.ShowNode(self.app, None) fake_node = mock.Mock( cluster_id=None, created_at="2015-02-10T12:03:16", data={}, details={"OS-DCF:diskConfig": "MANUAL"}, domain_id=None, id="d5779bb0-f0a0-49c9-88cc-6f078adb5a0b", index=-1, init_at="2015-02-10T12:03:13", metadata={}, physical_id="f41537fa-22ab-4bea-94c0-c874e19d0c80", profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", role=None, status="ACTIVE", status_reason="Creation succeeded", tainted=True, updated_at="2015-03-04T04:58:27", user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "my_node" fake_node.to_dict = mock.Mock( return_value={'details': {'key': 'value'}} ) self.mock_client.get_node = mock.Mock(return_value=fake_node) def test_node_show(self): arglist = ['my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_node.assert_called_with('my_node', details=False) def test_node_show_with_details(self): arglist = ['my_node', '--details'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_node.assert_called_with( 'my_node', details=True) def test_node_show_not_found(self): arglist = ['my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_node.side_effect = sdk_exc.ResourceNotFound() error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Node not found: my_node', str(error)) class TestNodeCreate(TestNode): defaults = { "cluster_id": None, "metadata": {}, "name": "my_node", "profile_id": "mystack", "role": None } def setUp(self): super(TestNodeCreate, self).setUp() self.cmd = osc_node.CreateNode(self.app, None) fake_node = mock.Mock( action="2366d440-c73e-4961-9254-6d1c3af7c167", cluster_id="", created_at=None, data={}, domain=None, id="0df0931b-e251-4f2e-8719-4ebfda3627ba", index=-1, init_time="2015-03-05T08:53:15", metadata={}, physical_id=None, profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", role="master", status="INIT", status_reason="Initializing", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "my_node" fake_node.to_dict = mock.Mock(return_value={}) self.mock_client.create_node = mock.Mock(return_value=fake_node) self.mock_client.get_node = mock.Mock(return_value=fake_node) def test_node_create_defaults(self): arglist = ['my_node', '--profile', 'mystack'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_node.assert_called_with(**self.defaults) def test_node_create_with_metadata(self): arglist = ['my_node', '--profile', 'mystack', '--metadata', 'key1=value1;key2=value2'] kwargs = copy.deepcopy(self.defaults) kwargs['metadata'] = {'key1': 'value1', 'key2': 'value2'} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_node.assert_called_with(**kwargs) def test_node_create_with_cluster(self): arglist = ['my_node', '--profile', 'mystack', '--cluster', 'mycluster'] kwargs = copy.deepcopy(self.defaults) kwargs['cluster_id'] = 'mycluster' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_node.assert_called_with(**kwargs) def test_node_create_with_role(self): arglist = ['my_node', '--profile', 'mystack', '--role', 'master'] kwargs = copy.deepcopy(self.defaults) kwargs['role'] = 'master' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_node.assert_called_with(**kwargs) class TestNodeUpdate(TestNode): defaults = { "name": "new_node", "metadata": { "nk1": "nv1", "nk2": "nv2", }, "profile_id": "new_profile", "role": "new_role", "tainted": True } def setUp(self): super(TestNodeUpdate, self).setUp() self.cmd = osc_node.UpdateNode(self.app, None) fake_node = mock.Mock( action="2366d440-c73e-4961-9254-6d1c3af7c167", cluster_id="", created_at=None, data={}, domain=None, id="0df0931b-e251-4f2e-8719-4ebfda3627ba", index=-1, init_time="2015-03-05T08:53:15", metadata={}, physical_id=None, profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", role="master", status="INIT", status_reason="Initializing", tainted=False, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "my_node" fake_node.to_dict = mock.Mock(return_value={}) self.mock_client.update_node = mock.Mock(return_value=fake_node) self.mock_client.get_node = mock.Mock(return_value=fake_node) self.mock_client.find_node = mock.Mock(return_value=fake_node) def test_node_update_defaults(self): arglist = ['--name', 'new_node', '--metadata', 'nk1=nv1;nk2=nv2', '--profile', 'new_profile', '--role', 'new_role', '--tainted', 'True', '0df0931b'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.update_node.assert_called_with( '0df0931b-e251-4f2e-8719-4ebfda3627ba', **self.defaults) def test_node_update_not_found(self): arglist = ['--name', 'new_node', '--metadata', 'nk1=nv1;nk2=nv2', '--profile', 'new_profile', '--role', 'new_role', 'c6b8b252'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.find_node.return_value = None error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Node not found: c6b8b252', str(error)) class TestNodeDelete(TestNode): def setUp(self): super(TestNodeDelete, self).setUp() self.cmd = osc_node.DeleteNode(self.app, None) mock_node = mock.Mock(location='loc/fake_action_id') self.mock_client.delete_node = mock.Mock(return_value=mock_node) def test_node_delete(self): arglist = ['node1', 'node2', 'node3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_node.assert_has_calls( [mock.call('node1', False, False), mock.call('node2', False, False), mock.call('node3', False, False)] ) def test_node_delete_force(self): arglist = ['node1', 'node2', 'node3', '--force'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_node.assert_has_calls( [mock.call('node1', False, False), mock.call('node2', False, False), mock.call('node3', False, False)] ) def test_node_delete_force_delete(self): arglist = ['node1', 'node2', 'node3', '--force-delete'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_node.assert_has_calls( [mock.call('node1', False, True), mock.call('node2', False, True), mock.call('node3', False, True)] ) def test_node_delete_not_found(self): arglist = ['my_node'] self.mock_client.delete_node.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_node.assert_has_calls( [mock.call('my_node', False, False)] ) def test_node_delete_one_found_one_not_found(self): arglist = ['node1', 'node2'] self.mock_client.delete_node.side_effect = ( [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_node.assert_has_calls( [mock.call('node1', False, False), mock.call('node2', False, False)] ) @mock.patch('sys.stdin', spec=six.StringIO) def test_node_delete_prompt_yes(self, mock_stdin): arglist = ['my_node'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'y' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_node.assert_called_with( 'my_node', False, False) @mock.patch('sys.stdin', spec=six.StringIO) def test_node_delete_prompt_no(self, mock_stdin): arglist = ['my_node'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'n' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_node.assert_not_called() class TestNodeCheck(TestNode): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestNodeCheck, self).setUp() self.cmd = osc_node.CheckNode(self.app, None) self.mock_client.check_node = mock.Mock( return_value=self.response) def test_node_check(self): arglist = ['node1', 'node2', 'node3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.check_node.assert_has_calls( [mock.call('node1'), mock.call('node2'), mock.call('node3')] ) def test_node_check_not_found(self): arglist = ['node1'] self.mock_client.check_node.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Node not found: node1', str(error)) class TestNodeRecover(TestNode): action_id = "8bb476c3-0f4c-44ee-9f64-c7b0260814de" def setUp(self): super(TestNodeRecover, self).setUp() self.cmd = osc_node.RecoverNode(self.app, None) fake_res = {'action': self.action_id} self.mock_client.recover_node = mock.Mock(return_value=fake_res) def test_node_recover(self): arglist = ['node1', 'node2', 'node3', '--check', 'false'] kwargs = {'check': False} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.recover_node.assert_has_calls( [mock.call('node1', **kwargs), mock.call('node2', **kwargs), mock.call('node3', **kwargs)] ) def test_node_recover_not_found(self): arglist = ['node1'] self.mock_client.recover_node.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Node not found: node1', str(error)) class TestNodeAdopt(TestNode): defaults = { "identity": "fake-resource-id", "metadata": {}, "name": "my_node", "overrides": {}, "role": None, "snapshot": False, "type": "os.nova.server-1.0" } def setUp(self): super(TestNodeAdopt, self).setUp() self.cmd = osc_node.AdoptNode(self.app, None) fake_node = mock.Mock( action="2366d440-c73e-4961-9254-6d1c3af7c167", cluster_id="", created_at=None, data={}, domain=None, id="0df0931b-e251-4f2e-8719-4ebfda3627ba", index=-1, init_time="2015-03-05T08:53:15", metadata={}, physical_id=None, profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", role="master", status="INIT", status_reason="Initializing", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "my_node" fake_node.to_dict = mock.Mock(return_value={}) self.mock_client.adopt_node = mock.Mock(return_value=fake_node) self.mock_client.get_node = mock.Mock(return_value=fake_node) def test_node_adopt_defaults(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--name', 'my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(False, **self.defaults) def test_node_adopt_with_metadata(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--metadata', 'key1=value1;key2=value2', '--name', 'my_node'] kwargs = copy.deepcopy(self.defaults) kwargs['metadata'] = {'key1': 'value1', 'key2': 'value2'} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(False, **kwargs) def test_node_adopt_with_override(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--overrides', '{"networks": [{"network": "fake-net-name"}]}', '--name', 'my_node'] kwargs = copy.deepcopy(self.defaults) kwargs['overrides'] = {'networks': [{'network': 'fake-net-name'}]} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(False, **kwargs) def test_node_adopt_with_role(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--role', 'master', '--name', 'my_node'] kwargs = copy.deepcopy(self.defaults) kwargs['role'] = 'master' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(False, **kwargs) def test_node_adopt_with_snapshot(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--snapshot', '--name', 'my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) kwargs = copy.deepcopy(self.defaults) kwargs['snapshot'] = True self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(False, **kwargs) class TestNodeAdoptPreview(TestNode): defaults = { "identity": "fake-resource-id", "overrides": {}, "snapshot": False, "type": "os.nova.server-1.0" } def setUp(self): super(TestNodeAdoptPreview, self).setUp() self.cmd = osc_node.AdoptNode(self.app, None) self.fake_node_preview = { "node_profile": { "node_preview": { "properties": { }, "type": "os.nova.server", "version": "1.0"} } } self.mock_client.adopt_node = mock.Mock( return_value=self.fake_node_preview) self.mock_client.get_node = mock.Mock( return_value=self.fake_node_preview) def test_node_adopt_preview_default(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--preview'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(True, **self.defaults) def test_node_adopt_preview_with_overrides(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--overrides', '{"networks": [{"network": "fake-net-name"}]}', '--preview'] parsed_args = self.check_parser(self.cmd, arglist, []) kwargs = copy.deepcopy(self.defaults) kwargs['overrides'] = {'networks': [{'network': 'fake-net-name'}]} self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(True, **kwargs) def test_node_adopt_preview_with_snapshot(self): arglist = ['--identity', 'fake-resource-id', '--type', 'os.nova.server-1.0', '--snapshot', '--preview'] parsed_args = self.check_parser(self.cmd, arglist, []) kwargs = copy.deepcopy(self.defaults) kwargs['snapshot'] = True self.cmd.take_action(parsed_args) self.mock_client.adopt_node.assert_called_with(True, **kwargs) class TestNodeOp(TestNode): response = {"action": "1db0f5c5-9183-4c47-9ef1-a5a97402a2c1"} def setUp(self): super(TestNodeOp, self).setUp() self.cmd = osc_node.NodeOp(self.app, None) self.mock_client.perform_operation_on_node = mock.Mock( return_value=self.response) def test_node_op(self): arglist = ['--operation', 'dance', '--params', 'style=tango', 'my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.perform_operation_on_node.assert_called_once_with( 'my_node', 'dance', style='tango') def test_node_op_not_found(self): arglist = ['--operation', 'dance', 'node1'] ex = sdk_exc.ResourceNotFound self.mock_client.perform_operation_on_node.side_effect = ex parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Node not found: node1', str(error)) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_profile.py0000664000175000017500000004171513643337345025774 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from osc_lib import utils import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import profile as osc_profile class TestProfile(fakes.TestClusteringv1): def setUp(self): super(TestProfile, self).setUp() self.mock_client = self.app.client_manager.clustering class TestProfileShow(TestProfile): response = {"profile": { "created_at": "2015-03-01T14:28:25", "domain": 'false', "id": "7fa885cd-fa39-4531-a42d-780af95c84a4", "metadata": {}, "name": "test_prof1", "project": "42d9e9663331431f97b75e25136307ff", "spec": { "disable_rollback": 'false', "environment": { "resource_registry": { "os.heat.server": "OS::Heat::Server" } }, "files": { "file:///opt/stack/senlin/examples/profiles/test_script.sh": "#!/bin/bash\n\necho \"this is a test script file\"\n" }, "parameters": {}, "template": { "heat_template_version": "2014-10-16", "outputs": { "result": { "value": { "get_attr": [ "random", "value" ] } } }, "parameters": { "file": { "default": { "get_file": "file:///opt/stack/senlin/" "examples/profiles/test_script.sh" }, "type": "string" } }, "resources": { "random": { "properties": { "length": 64 }, "type": "OS::Heat::RandomString" } }, "timeout": 60 }, "type": "os.heat.stack", "version": "1.0" }, "type": "os.heat.stack-1.0", "updated_at": 'null', "user": "5e5bf8027826429c96af157f68dc9072" }} def setUp(self): super(TestProfileShow, self).setUp() self.cmd = osc_profile.ShowProfile(self.app, None) fake_profile = mock.Mock( created_at="2015-03-01T14:28:25", domain_id=None, id="7fa885cd-fa39-4531-a42d-780af95c84a4", metadata={}, project_id="42d9e9663331431f97b75e25136307ff", spec={"foo": 'bar'}, type="os.heat.stack-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_profile.name = "test_prof1" fake_profile.to_dict = mock.Mock(return_value={}) self.mock_client.get_profile = mock.Mock(return_value=fake_profile) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_show(self): arglist = ['my_profile'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_profile.assert_called_with('my_profile') profile = self.mock_client.get_profile('my_profile') self.assertEqual("42d9e9663331431f97b75e25136307ff", profile.project_id) self.assertEqual("7fa885cd-fa39-4531-a42d-780af95c84a4", profile.id) self.assertEqual({}, profile.metadata) self.assertEqual("test_prof1", profile.name) self.assertEqual("os.heat.stack-1.0", profile.type) def test_profile_show_not_found(self): arglist = ['my_profile'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_profile.side_effect = sdk_exc.ResourceNotFound() self.assertRaises( exc.CommandError, self.cmd.take_action, parsed_args) class TestProfileList(TestProfile): def setUp(self): super(TestProfileList, self).setUp() self.cmd = osc_profile.ListProfile(self.app, None) fake_profile = mock.Mock( created_at="2015-03-01T14:28:25", domain_id=None, id="7fa885cd-fa39-4531-a42d-780af95c84a4", metadata={}, project_id="42d9e9663331431f97b75e25136307ff", spec={"foo": 'bar'}, type="os.heat.stack-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_profile.name = "test_profile" fake_profile.to_dict = mock.Mock(return_value={}) self.mock_client.profiles = mock.Mock(return_value=[fake_profile]) self.defaults = { 'limit': None, 'marker': None, 'sort': None, 'global_project': False, } self.columns = ['id', 'name', 'type', 'created_at'] def test_profile_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.profiles.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_profile_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.profiles.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_profile_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.profiles.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_profile_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'id:asc' arglist = ['--sort', 'id:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.profiles.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_profile_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.profiles.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_profile_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'id:bad_direction' arglist = ['--sort', 'id:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.profiles.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_profile_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_profile' arglist = ['--filter', 'name=my_profile'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.profiles.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestProfileDelete(TestProfile): def setUp(self): super(TestProfileDelete, self).setUp() self.cmd = osc_profile.DeleteProfile(self.app, None) self.mock_client.delete_profile = mock.Mock() def test_profile_delete(self): arglist = ['profile1', 'profile2', 'profile3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_profile.assert_has_calls( [mock.call('profile1', False), mock.call('profile2', False), mock.call('profile3', False)] ) def test_profile_delete_force(self): arglist = ['profile1', 'profile2', 'profile3', '--force'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_profile.assert_has_calls( [mock.call('profile1', False), mock.call('profile2', False), mock.call('profile3', False)] ) def test_profile_delete_not_found(self): arglist = ['my_profile'] self.mock_client.delete_profile.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Failed to delete 1 of the 1 specified profile(s).', str(error)) def test_profile_delete_one_found_one_not_found(self): arglist = ['profile1', 'profile2'] self.mock_client.delete_profile.side_effect = ( [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.mock_client.delete_profile.assert_has_calls( [mock.call('profile1', False), mock.call('profile2', False)] ) self.assertEqual('Failed to delete 1 of the 2 specified profile(s).', str(error)) @mock.patch('sys.stdin', spec=six.StringIO) def test_profile_delete_prompt_yes(self, mock_stdin): arglist = ['my_profile'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'y' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_profile.assert_called_with('my_profile', False) @mock.patch('sys.stdin', spec=six.StringIO) def test_profile_delete_prompt_no(self, mock_stdin): arglist = ['my_profile'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'n' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_profile.assert_not_called() class TestProfileCreate(TestProfile): spec_path = 'senlinclient/tests/test_specs/nova_server.yaml' def setUp(self): super(TestProfileCreate, self).setUp() self.cmd = osc_profile.CreateProfile(self.app, None) fake_profile = mock.Mock( created_at="2015-03-01T14:28:25", domain_id=None, id="7fa885cd-fa39-4531-a42d-780af95c84a4", metadata={}, project_id="42d9e9663331431f97b75e25136307ff", spec={"foo": 'bar'}, type="os.heat.stack-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_profile.name = "test_profile" fake_profile.to_dict = mock.Mock(return_value={}) self.mock_client.create_profile = mock.Mock(return_value=fake_profile) self.mock_client.get_profile = mock.Mock(return_value=fake_profile) utils.get_dict_properties = mock.Mock(return_value='') self.defaults = { "spec": { "version": 1.0, "type": "os.nova.server", "properties": { "flavor": 1, "name": "cirros_server", "image": "cirros-0.3.4-x86_64-uec" }, }, "name": "my_profile", "metadata": {} } def test_profile_create_defaults(self): arglist = ['my_profile', '--spec-file', self.spec_path] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_profile.assert_called_with(**self.defaults) def test_profile_create_metadata(self): arglist = ['my_profile', '--spec-file', self.spec_path, '--metadata', 'key1=value1'] kwargs = copy.deepcopy(self.defaults) kwargs['metadata'] = {'key1': 'value1'} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_profile.assert_called_with(**kwargs) class TestProfileUpdate(TestProfile): def setUp(self): super(TestProfileUpdate, self).setUp() self.cmd = osc_profile.UpdateProfile(self.app, None) fake_profile = mock.Mock( created_at="2015-03-01T14:28:25", domain_id=None, id="7fa885cd-fa39-4531-a42d-780af95c84a4", metadata={}, project_id="42d9e9663331431f97b75e25136307ff", spec={"foo": 'bar'}, type="os.heat.stack-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_profile.name = "test_profile" fake_profile.to_dict = mock.Mock(return_value={}) self.mock_client.update_profile = mock.Mock(return_value=fake_profile) self.mock_client.get_profile = mock.Mock(return_value=fake_profile) self.mock_client.find_profile = mock.Mock(return_value=fake_profile) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_update_defaults(self): arglist = ['--name', 'new_profile', '--metadata', 'nk1=nv1;nk2=nv2', 'e3057c77'] parsed_args = self.check_parser(self.cmd, arglist, []) defaults = { "name": "new_profile", "metadata": { "nk1": "nv1", "nk2": "nv2", } } self.cmd.take_action(parsed_args) self.mock_client.update_profile.assert_called_with( "7fa885cd-fa39-4531-a42d-780af95c84a4", **defaults) def test_profile_update_not_found(self): arglist = ['--name', 'new_profile', '--metadata', 'nk1=nv1;nk2=nv2', 'c6b8b252'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.find_profile.return_value = None error = self.assertRaises( exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Profile not found: c6b8b252', str(error)) class TestProfileValidate(TestProfile): spec_path = 'senlinclient/tests/test_specs/nova_server.yaml' defaults = { "spec": { "version": 1.0, "type": "os.nova.server", "properties": { "flavor": 1, "name": "cirros_server", "image": "cirros-0.3.4-x86_64-uec" }, } } def setUp(self): super(TestProfileValidate, self).setUp() self.cmd = osc_profile.ValidateProfile(self.app, None) fake_profile = mock.Mock( created_at=None, domain_id=None, id=None, metadata={}, project_id="42d9e9663331431f97b75e25136307ff", spec={"foo": 'bar'}, type="os.heat.stack-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_profile.name = "test_profile" fake_profile.to_dict = mock.Mock(return_value={}) self.mock_client.validate_profile = mock.Mock( return_value=fake_profile) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_validate(self): arglist = ['--spec-file', self.spec_path] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.validate_profile.assert_called_with(**self.defaults) profile = self.mock_client.validate_profile(**self.defaults) self.assertEqual("42d9e9663331431f97b75e25136307ff", profile.project_id) self.assertEqual("5e5bf8027826429c96af157f68dc9072", profile.user_id) self.assertIsNone(profile.id) self.assertEqual({}, profile.metadata) self.assertEqual("test_profile", profile.name) self.assertEqual("os.heat.stack-1.0", profile.type) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_policy.py0000664000175000017500000004201613643337345025626 0ustar zuulzuul00000000000000# 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. import copy import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import policy as osc_policy class TestPolicy(fakes.TestClusteringv1): def setUp(self): super(TestPolicy, self).setUp() self.mock_client = self.app.client_manager.clustering class TestPolicyList(TestPolicy): columns = ['id', 'name', 'type', 'created_at'] response = {"policies": [ { } ]} defaults = { 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestPolicyList, self).setUp() self.cmd = osc_policy.ListPolicy(self.app, None) fake_policy = mock.Mock( created_at="2015-02-15T08:33:13.000000", data={}, domain=None, id="7192d8df-73be-4e98-ab99-1cf6d5066729", project_id="42d9e9663331431f97b75e25136307ff", spec={ "description": "A test policy", "properties": { "criteria": "OLDEST_FIRST", "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False }, "type": "senlin.policy.deletion", "version": "1.0" }, type="senlin.policy.deletion-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_policy.name = "test_policy_1" fake_policy.to_dict = mock.Mock(return_value={}) self.mock_client.policies = mock.Mock( return_value=self.response) def test_policy_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_policy_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_policy_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_policy_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_policy_list_sort_invalid_key(self): self.mock_client.policies = mock.Mock( return_value=self.response) kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.policies.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_policy_list_sort_invalid_direction(self): self.mock_client.policies = mock.Mock( return_value=self.response) kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.policies.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_policy_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_policy_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_policy' arglist = ['--filter', 'name=my_policy'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.policies.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestPolicyShow(TestPolicy): response = {"policy": { }} def setUp(self): super(TestPolicyShow, self).setUp() self.cmd = osc_policy.ShowPolicy(self.app, None) fake_policy = mock.Mock( created_at="2015-03-02T07:40:31", data={}, domain_id=None, id="02f62195-2198-4797-b0a9-877632208527", project_id="42d9e9663331431f97b75e25136307ff", spec={ "properties": { "adjustment": { "best_effort": True, "min_step": 1, "number": 1, "type": "CHANGE_IN_CAPACITY" }, "event": "CLUSTER_SCALE_IN" }, "type": "senlin.policy.scaling", "version": "1.0" }, type="senlin.policy.scaling-1.0", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_policy.name = "sp001" fake_policy.to_dict = mock.Mock(return_value={}) self.mock_client.get_policy = mock.Mock(return_value=fake_policy) def test_policy_show(self): arglist = ['sp001'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_policy.assert_called_with('sp001') policy = self.mock_client.get_policy('sp001') self.assertEqual("2015-03-02T07:40:31", policy.created_at) self.assertEqual({}, policy.data) self.assertEqual("02f62195-2198-4797-b0a9-877632208527", policy.id) self.assertEqual("sp001", policy.name) self.assertEqual("42d9e9663331431f97b75e25136307ff", policy.project_id) self.assertEqual("senlin.policy.scaling-1.0", policy.type) self.assertIsNone(policy.updated_at) def test_policy_show_not_found(self): arglist = ['sp001'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_policy.side_effect = sdk_exc.ResourceNotFound() self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) class TestPolicyCreate(TestPolicy): spec_path = 'senlinclient/tests/test_specs/deletion_policy.yaml' defaults = { "name": "my_policy", "spec": { "version": 1, "type": "senlin.policy.deletion", "description": "A policy for choosing victim node(s) from a " "cluster for deletion.", "properties": { "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False, "criteria": "OLDEST_FIRST" } } } def setUp(self): super(TestPolicyCreate, self).setUp() self.cmd = osc_policy.CreatePolicy(self.app, None) fake_policy = mock.Mock( created_at="2016-02-21T02:38:36", data={}, domain_id=None, id="9f779ddf-744e-48bd-954c-acef7e11116c", project_id="5f1cc92b578e4e25a3b284179cf20a9b", spec={ "description": "A policy for choosing victim node(s) from a " "cluster for deletion.", "properties": { "criteria": "OLDEST_FIRST", "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False }, "type": "senlin.policy.deletion", "version": 1.0 }, type="senlin.policy.deletion-1.0", updated_at=None, user_id="2d7aca950f3e465d8ef0c81720faf6ff" ) fake_policy.name = "my_policy" fake_policy.to_dict = mock.Mock(return_value={}) self.mock_client.create_policy = mock.Mock(return_value=fake_policy) self.mock_client.get_policy = mock.Mock(return_value=fake_policy) def test_policy_create_defaults(self): arglist = ['my_policy', '--spec-file', self.spec_path] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_policy.assert_called_with(**self.defaults) class TestPolicyUpdate(TestPolicy): def setUp(self): super(TestPolicyUpdate, self).setUp() self.cmd = osc_policy.UpdatePolicy(self.app, None) fake_policy = mock.Mock( created_at="2016-02-21T02:38:36", data={}, domain_id=None, id="9f779ddf-744e-48bd-954c-acef7e11116c", project_id="5f1cc92b578e4e25a3b284179cf20a9b", spec={ "description": "A policy for choosing victim node(s) from a " "cluster for deletion.", "properties": { "criteria": "OLDEST_FIRST", "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False }, "type": "senlin.policy.deletion", "version": 1.0 }, type="senlin.policy.deletion-1.0", updated_at=None, user_id="2d7aca950f3e465d8ef0c81720faf6ff" ) fake_policy.name = "new_policy" fake_policy.to_dict = mock.Mock(return_value={}) self.mock_client.update_policy = mock.Mock(return_value=fake_policy) self.mock_client.get_policy = mock.Mock(return_value=fake_policy) self.mock_client.find_policy = mock.Mock(return_value=fake_policy) def test_policy_update_defaults(self): arglist = ['--name', 'new_policy', '9f779ddf'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.update_policy.assert_called_with( '9f779ddf-744e-48bd-954c-acef7e11116c', name="new_policy") def test_policy_update_not_found(self): arglist = ['--name', 'new_policy', 'c6b8b252'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.find_policy.return_value = None error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Policy not found: c6b8b252', str(error)) class TestPolicyDelete(TestPolicy): def setUp(self): super(TestPolicyDelete, self).setUp() self.cmd = osc_policy.DeletePolicy(self.app, None) self.mock_client.delete_policy = mock.Mock() def test_policy_delete(self): arglist = ['policy1', 'policy2', 'policy3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_policy.assert_has_calls( [mock.call('policy1', False), mock.call('policy2', False), mock.call('policy3', False)] ) def test_policy_delete_force(self): arglist = ['policy1', 'policy2', 'policy3', '--force'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_policy.assert_has_calls( [mock.call('policy1', False), mock.call('policy2', False), mock.call('policy3', False)] ) def test_policy_delete_not_found(self): arglist = ['my_policy'] self.mock_client.delete_policy.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Failed to delete 1 of the 1 specified policy(s).', str(error)) def test_policy_delete_one_found_one_not_found(self): arglist = ['policy1', 'policy2'] self.mock_client.delete_policy.side_effect = ( [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.mock_client.delete_policy.assert_has_calls( [mock.call('policy1', False), mock.call('policy2', False)] ) self.assertEqual('Failed to delete 1 of the 2 specified policy(s).', str(error)) @mock.patch('sys.stdin', spec=six.StringIO) def test_policy_delete_prompt_yes(self, mock_stdin): arglist = ['my_policy'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'y' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_policy.assert_called_with('my_policy', False) @mock.patch('sys.stdin', spec=six.StringIO) def test_policy_delete_prompt_no(self, mock_stdin): arglist = ['my_policy'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'n' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_policy.assert_not_called() class TestPolicyValidate(TestPolicy): spec_path = 'senlinclient/tests/test_specs/deletion_policy.yaml' defaults = { "spec": { "version": 1, "type": "senlin.policy.deletion", "description": "A policy for choosing victim node(s) from a " "cluster for deletion.", "properties": { "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False, "criteria": "OLDEST_FIRST" } } } def setUp(self): super(TestPolicyValidate, self).setUp() self.cmd = osc_policy.ValidatePolicy(self.app, None) fake_policy = mock.Mock( created_at=None, data={}, domain_id=None, id=None, project_id="5f1cc92b578e4e25a3b284179cf20a9b", spec={ "description": "A policy for choosing victim node(s) from a " "cluster for deletion.", "properties": { "criteria": "OLDEST_FIRST", "destroy_after_deletion": True, "grace_period": 60, "reduce_desired_capacity": False }, "type": "senlin.policy.deletion", "version": 1.0 }, type="senlin.policy.deletion-1.0", updated_at=None, user_id="2d7aca950f3e465d8ef0c81720faf6ff" ) fake_policy.name = "validated_policy" fake_policy.to_dict = mock.Mock(return_value={}) self.mock_client.validate_policy = mock.Mock(return_value=fake_policy) def test_policy_validate(self): arglist = ['--spec-file', self.spec_path] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.validate_policy.assert_called_with(**self.defaults) policy = self.mock_client.validate_policy(**self.defaults) self.assertEqual("5f1cc92b578e4e25a3b284179cf20a9b", policy.project_id) self.assertEqual({}, policy.data) self.assertIsNone(policy.id) self.assertEqual("validated_policy", policy.name) self.assertEqual("senlin.policy.deletion-1.0", policy.type) self.assertIsNone(policy.updated_at) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_policy_type.py0000664000175000017500000000665313643337345026676 0ustar zuulzuul00000000000000# 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. import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import policy_type as osc_policy_type class TestPolicyType(fakes.TestClusteringv1): def setUp(self): super(TestPolicyType, self).setUp() self.mock_client = self.app.client_manager.clustering class TestPolicyTypeList(TestPolicyType): expected_columns = ['name', 'version', 'support_status'] pt1 = mock.Mock( schema={'foo': 'bar'}, support_status={ "1.0": [{"status": "SUPPORTED", "since": "2016.10"}] } ) pt1.name = 'BBB' pt2 = mock.Mock( schema={'foo': 'bar'}, support_status={ "1.0": [{"status": "DEPRECATED", "since": "2016.01"}] } ) pt2.name = 'AAA' list_response = [pt1, pt2] expected_rows = [ ('AAA', '1.0', 'DEPRECATED since 2016.01'), ('BBB', '1.0', 'SUPPORTED since 2016.10') ] def setUp(self): super(TestPolicyTypeList, self).setUp() self.cmd = osc_policy_type.PolicyTypeList(self.app, None) self.mock_client.policy_types = mock.Mock( return_value=self.list_response) def test_policy_type_list(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) expected_columns = self.expected_columns expected_rows = self.expected_rows columns, rows = self.cmd.take_action(parsed_args) if len(columns) == 2: expected_columns = ['name', 'version'] expected_rows = [ ('CCC', '1.0') ] self.mock_client.policy_types.assert_called_with() self.assertEqual(expected_columns, columns) self.assertEqual(expected_rows, rows) class TestPolicyTypeShow(TestPolicyType): def setUp(self): super(TestPolicyTypeShow, self).setUp() self.cmd = osc_policy_type.PolicyTypeShow(self.app, None) fake_pt = mock.Mock(schema={'foo': 'bar'}) fake_pt.name = 'senlin.policy.deletion-1.0' self.mock_client.get_policy_type = mock.Mock(return_value=fake_pt) def test_policy_type_show(self): arglist = ['os.heat.stack-1.0'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_policy_type.assert_called_once_with( 'os.heat.stack-1.0') def test_policy_type_show_not_found(self): arglist = ['senlin.policy.deletion-1.0'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_policy_type.side_effect = ( sdk_exc.ResourceNotFound()) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Policy Type not found: senlin.policy.deletion-1.0', str(error)) python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_cluster.py0000664000175000017500000012346213643337345026015 0ustar zuulzuul00000000000000# 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. import copy import subprocess import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster as osc_cluster class TestCluster(fakes.TestClusteringv1): def setUp(self): super(TestCluster, self).setUp() self.mock_client = self.app.client_manager.clustering class TestClusterList(TestCluster): columns = ['id', 'name', 'status', 'created_at', 'updated_at'] defaults = { 'global_project': False, 'marker': None, 'limit': None, 'sort': None, } def setUp(self): super(TestClusterList, self).setUp() self.cmd = osc_cluster.ListCluster(self.app, None) fake_cluster = mock.Mock( created_at="2015-02-10T14:26:14", data={}, desired_capacity=4, domain_id=None, id="7d85f602-a948-4a30-afd4-e84f47471c15", init_time="2015-02-10T14:26:11", max_size=-1, metadata={}, min_size=0, node_ids=[ "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", "da1e9c87-e584-4626-a120-022da5062dac" ], policies=[], profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="ACTIVE", status_reason="Cluster scale-in succeeded", timeout=3600, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_cluster.name = "cluster1" fake_cluster.to_dict = mock.Mock(return_value={}) self.mock_client.clusters = mock.Mock(return_value=[fake_cluster]) def test_cluster_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_cluster_list_full_id(self): arglist = ['--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**self.defaults) self.assertEqual(self.columns, columns) def test_cluster_list_limit(self): kwargs = copy.deepcopy(self.defaults) kwargs['limit'] = '3' arglist = ['--limit', '3'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_cluster_list_sort(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:asc' arglist = ['--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_cluster_list_sort_invalid_key(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.clusters.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_cluster_list_sort_invalid_direction(self): kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.clusters.side_effect = sdk_exc.HttpException() self.assertRaises(sdk_exc.HttpException, self.cmd.take_action, parsed_args) def test_cluster_list_filter(self): kwargs = copy.deepcopy(self.defaults) kwargs['name'] = 'my_cluster' arglist = ['--filter', 'name=my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_cluster_list_marker(self): kwargs = copy.deepcopy(self.defaults) kwargs['marker'] = 'a9448bf6' arglist = ['--marker', 'a9448bf6'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.clusters.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestClusterShow(TestCluster): def setUp(self): super(TestClusterShow, self).setUp() self.cmd = osc_cluster.ShowCluster(self.app, None) fake_cluster = mock.Mock( config={}, created_at="2015-02-11T15:13:20", data={}, desired_capacity=0, domain_id=None, id="7d85f602-a948-4a30-afd4-e84f47471c15", init_time="2015-02-10T14:26:11", max_size=-1, metadata={}, min_size=0, node_ids=[], policies=[], profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="ACTIVE", status_reason="Cluster scale-in succeeded", timeout=3600, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_cluster.name = "my_cluster" fake_cluster.to_dict = mock.Mock(return_value={}) self.mock_client.get_cluster = mock.Mock(return_value=fake_cluster) def test_cluster_show(self): arglist = ['my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_cluster.assert_called_with('my_cluster') def test_cluster_show_not_found(self): arglist = ['my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_cluster.side_effect = sdk_exc.ResourceNotFound() error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Cluster not found: my_cluster', str(error)) class TestClusterCreate(TestCluster): defaults = { "config": {}, "desired_capacity": 0, "max_size": -1, "metadata": {}, "min_size": 0, "name": "test_cluster", "profile_id": "mystack", "timeout": None } def setUp(self): super(TestClusterCreate, self).setUp() self.cmd = osc_cluster.CreateCluster(self.app, None) fake_cluster = mock.Mock( config={}, created_at="2015-02-11T15:13:20", data={}, desired_capacity=0, domain_id=None, id="7d85f602-a948-4a30-afd4-e84f47471c15", init_time="2015-02-10T14:26:11", max_size=-1, metadata={}, min_size=0, node_ids=[], policies=[], profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="ACTIVE", status_reason="Cluster scale-in succeeded", timeout=3600, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_cluster.name = "my_cluster" fake_cluster.to_dict = mock.Mock(return_value={}) self.mock_client.create_cluster = mock.Mock(return_value=fake_cluster) self.mock_client.get_cluster = mock.Mock(return_value=fake_cluster) def test_cluster_create_defaults(self): arglist = ['test_cluster', '--profile', 'mystack'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_cluster.assert_called_with(**self.defaults) def test_cluster_create_with_metadata(self): arglist = ['test_cluster', '--profile', 'mystack', '--metadata', 'key1=value1;key2=value2'] kwargs = copy.deepcopy(self.defaults) kwargs['metadata'] = {'key1': 'value1', 'key2': 'value2'} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_cluster.assert_called_with(**kwargs) def test_cluster_create_with_config(self): arglist = ['test_cluster', '--profile', 'mystack', '--config', 'key1=value1;key2=value2'] kwargs = copy.deepcopy(self.defaults) kwargs['config'] = {'key1': 'value1', 'key2': 'value2'} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_cluster.assert_called_with(**kwargs) def test_cluster_create_with_size(self): arglist = ['test_cluster', '--profile', 'mystack', '--min-size', '1', '--max-size', '10', '--desired-capacity', '2'] kwargs = copy.deepcopy(self.defaults) kwargs['min_size'] = '1' kwargs['max_size'] = '10' kwargs['desired_capacity'] = '2' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.create_cluster.assert_called_with(**kwargs) class TestClusterUpdate(TestCluster): defaults = { "metadata": { "nk1": "nv1", "nk2": "nv2", }, "name": 'new_cluster', "profile_id": 'new_profile', "profile_only": False, "timeout": "30" } def setUp(self): super(TestClusterUpdate, self).setUp() self.cmd = osc_cluster.UpdateCluster(self.app, None) self.fake_cluster = mock.Mock( created_at="2015-02-11T15:13:20", data={}, desired_capacity=0, domain_id=None, id="7d85f602-a948-4a30-afd4-e84f47471c15", init_time="2015-02-10T14:26:11", max_size=-1, metadata={}, min_size=0, node_ids=[], policies=[], profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="ACTIVE", status_reason="Cluster scale-in succeeded", timeout=3600, updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) self.fake_cluster.name = "my_cluster" self.fake_cluster.to_dict = mock.Mock(return_value={}) self.mock_client.update_cluster = mock.Mock( return_value=self.fake_cluster) self.mock_client.get_cluster = mock.Mock( return_value=self.fake_cluster) self.mock_client.find_cluster = mock.Mock( return_value=self.fake_cluster) def test_cluster_update_defaults(self): arglist = ['--name', 'new_cluster', '--metadata', 'nk1=nv1;nk2=nv2', '--profile', 'new_profile', '--timeout', '30', '45edadcb'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.update_cluster.assert_called_with( self.fake_cluster, **self.defaults) def test_cluster_update_not_found(self): arglist = ['--name', 'new_cluster', '--metadata', 'nk1=nv1;nk2=nv2', '--profile', 'new_profile', 'c6b8b252'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.find_cluster.return_value = None error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Cluster not found: c6b8b252', str(error)) class TestClusterDelete(TestCluster): def setUp(self): super(TestClusterDelete, self).setUp() self.cmd = osc_cluster.DeleteCluster(self.app, None) mock_cluster = mock.Mock(location='abc/fake_action_id') self.mock_client.delete_cluster = mock.Mock(return_value=mock_cluster) def test_cluster_delete(self): arglist = ['cluster1', 'cluster2', 'cluster3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_cluster.assert_has_calls( [mock.call('cluster1', False, False), mock.call('cluster2', False, False), mock.call('cluster3', False, False)] ) def test_cluster_delete_force(self): arglist = ['cluster1', 'cluster2', 'cluster3', '--force'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_cluster.assert_has_calls( [mock.call('cluster1', False, False), mock.call('cluster2', False, False), mock.call('cluster3', False, False)] ) def test_cluster_delete_force_delete(self): arglist = ['cluster1', 'cluster2', 'cluster3', '--force-delete'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_cluster.assert_has_calls( [mock.call('cluster1', False, True), mock.call('cluster2', False, True), mock.call('cluster3', False, True)] ) def test_cluster_delete_not_found(self): arglist = ['my_cluster'] self.mock_client.delete_cluster.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_cluster.assert_has_calls( [mock.call('my_cluster', False, False)] ) def test_cluster_delete_one_found_one_not_found(self): arglist = ['cluster1', 'cluster2'] self.mock_client.delete_cluster.side_effect = ( [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.delete_cluster.assert_has_calls( [mock.call('cluster1', False, False), mock.call('cluster2', False, False)] ) @mock.patch('sys.stdin', spec=six.StringIO) def test_cluster_delete_prompt_yes(self, mock_stdin): arglist = ['my_cluster'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'y' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_cluster.assert_called_with( 'my_cluster', False, False) @mock.patch('sys.stdin', spec=six.StringIO) def test_cluster_delete_prompt_no(self, mock_stdin): arglist = ['my_cluster'] mock_stdin.isatty.return_value = True mock_stdin.readline.return_value = 'n' parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) mock_stdin.readline.assert_called_with() self.mock_client.delete_cluster.assert_not_called() class TestClusterResize(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} defaults = { "min_step": None, "adjustment_type": "EXACT_CAPACITY", "number": 2, "min_size": 1, "strict": True, "max_size": 20} def setUp(self): super(TestClusterResize, self).setUp() self.cmd = osc_cluster.ResizeCluster(self.app, None) self.mock_client.resize_cluster = mock.Mock( return_value=self.response) def test_cluster_resize_no_params(self): arglist = ['my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual(("At least one parameter of 'capacity', " "'adjustment', 'percentage', 'min_size' and " "'max_size' should be specified."), str(error)) def test_cluster_resize_multi_params(self): arglist = ['--capacity', '2', '--percentage', '50.0', '--adjustment', '1', '--min-size', '1', '--max-size', '20', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual("Only one of 'capacity', 'adjustment' " "and 'percentage' can be specified.", str(error)) def test_cluster_resize_capacity(self): arglist = ['--capacity', '2', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.resize_cluster.assert_called_with('my_cluster', **self.defaults) def test_cluster_resize_invalid_capacity(self): arglist = ['--capacity', '-1', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Cluster capacity must be larger than or equal to' ' zero.', str(error)) def test_cluster_resize_adjustment(self): arglist = ['--adjustment', '1', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] kwargs = copy.deepcopy(self.defaults) kwargs['adjustment_type'] = 'CHANGE_IN_CAPACITY' kwargs['number'] = 1 parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.resize_cluster.assert_called_with('my_cluster', **kwargs) def test_cluster_resize_invalid_adjustment(self): arglist = ['--adjustment', '0', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Adjustment cannot be zero.', str(error)) def test_cluster_resize_percentage(self): arglist = ['--percentage', '50.0', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) kwargs = copy.deepcopy(self.defaults) kwargs['adjustment_type'] = 'CHANGE_IN_PERCENTAGE' kwargs['number'] = 50.0 self.cmd.take_action(parsed_args) self.mock_client.resize_cluster.assert_called_with('my_cluster', **kwargs) def test_cluster_resize_invalid_percentage(self): arglist = ['--percentage', '0', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Percentage cannot be zero.', str(error)) def test_cluster_resize_invalid_min_step_capacity(self): arglist = ['--capacity', '2', '--min-size', '1', '--max-size', '20', 'my_cluster', '--strict', '--min-step', '1'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Min step is only used with percentage.', str(error)) def test_cluster_resize_invalid_min_size_capacity(self): arglist = ['--capacity', '2', '--min-size', '-1', '--max-size', '20', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Min size cannot be less than zero.', str(error)) def test_cluster_resize_invalid_max_size_capacity(self): arglist = ['--capacity', '2', '--min-size', '5', '--max-size', '3', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Min size cannot be larger than max size.', str(error)) def test_cluster_resize_min_size_larger_than_capacity(self): arglist = ['--capacity', '3', '--min-size', '5', '--max-size', '10', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Min size cannot be larger than the specified ' 'capacity', str(error)) def test_cluster_resize_invalid_max_size_less_capacity(self): arglist = ['--capacity', '15', '--min-size', '5', '--max-size', '10', 'my_cluster', '--strict'] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Max size cannot be less than the specified ' 'capacity.', str(error)) class TestClusterScaleIn(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterScaleIn, self).setUp() self.cmd = osc_cluster.ScaleInCluster(self.app, None) self.mock_client.scale_in_cluster = mock.Mock( return_value=self.response) def test_cluster_scale_in(self): arglist = ['--count', '2', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.scale_in_cluster.assert_called_with('my_cluster', '2') class TestClusterScaleOut(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterScaleOut, self).setUp() self.cmd = osc_cluster.ScaleOutCluster(self.app, None) self.mock_client.scale_out_cluster = mock.Mock( return_value=self.response) def test_cluster_scale_out(self): arglist = ['--count', '2', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.scale_out_cluster.assert_called_with('my_cluster', '2') class TestClusterPolicyAttach(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterPolicyAttach, self).setUp() self.cmd = osc_cluster.ClusterPolicyAttach(self.app, None) self.mock_client.attach_policy_to_cluster = mock.Mock( return_value=self.response) def test_cluster_policy_attach(self): arglist = ['--policy', 'my_policy', '--enabled', 'True', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.attach_policy_to_cluster.assert_called_with( 'my_cluster', 'my_policy', enabled=True) class TestClusterPolicyDetach(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterPolicyDetach, self).setUp() self.cmd = osc_cluster.ClusterPolicyDetach(self.app, None) self.mock_client.detach_policy_from_cluster = mock.Mock( return_value=self.response) def test_cluster_policy_detach(self): arglist = ['--policy', 'my_policy', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.detach_policy_from_cluster.assert_called_with( 'my_cluster', 'my_policy') class TestClusterNodeList(TestCluster): columns = ['id', 'name', 'index', 'status', 'physical_id', 'created_at'] args = { 'cluster_id': 'my_cluster', 'marker': 'a9448bf6', 'limit': '3', 'sort': None, } def setUp(self): super(TestClusterNodeList, self).setUp() self.cmd = osc_cluster.ClusterNodeList(self.app, None) fake_node = mock.Mock( cluster_id="", created_at="2015-02-11T15:13:20", data={}, details={}, domain_id=None, id="7d85f602-a948-4a30-afd4-e84f47471c15", index=-1, init_at="2015-02-10T14:26:11", metadata={}, phyiscal_id="cc028275-d078-4729-bf3e-154b7359814b", profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a", profile_name="mystack", project_id="6e18cc2bdbeb48a5b3cad2dc499f6804", status="ACTIVE", status_reason="Creation succeeded", updated_at=None, user_id="5e5bf8027826429c96af157f68dc9072" ) fake_node.name = "node001" fake_node.to_dict = mock.Mock(return_value={}) self.mock_client.nodes = mock.Mock(return_value=[fake_node]) def test_cluster_node_list(self): arglist = ['--limit', '3', '--marker', 'a9448bf6', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**self.args) self.assertEqual(self.columns, columns) def test_cluster_node_list_full_id(self): arglist = ['--limit', '3', '--marker', 'a9448bf6', 'my_cluster', '--full-id'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**self.args) self.assertEqual(self.columns, columns) def test_cluster_node_list_filter(self): kwargs = copy.deepcopy(self.args) kwargs['name'] = 'my_node' arglist = ['--limit', '3', '--marker', 'a9448bf6', 'my_cluster', '--filter', 'name=my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) def test_cluster_node_list_sort(self): kwargs = copy.deepcopy(self.args) kwargs['name'] = 'my_node' kwargs['sort'] = 'name:asc' arglist = ['--limit', '3', '--marker', 'a9448bf6', 'my_cluster', '--filter', 'name=my_node', '--sort', 'name:asc'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.nodes.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) class TestClusterNodeAdd(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterNodeAdd, self).setUp() self.cmd = osc_cluster.ClusterNodeAdd(self.app, None) self.mock_client.add_nodes_to_cluster = mock.Mock( return_value=self.response) def test_cluster_node_add(self): arglist = ['--nodes', 'node1', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.add_nodes_to_cluster.assert_called_with( 'my_cluster', ['node1']) def test_cluster_node_add_multi(self): arglist = ['--nodes', 'node1,node2', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.add_nodes_to_cluster.assert_called_with( 'my_cluster', ['node1', 'node2']) class TestClusterNodeDel(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterNodeDel, self).setUp() self.cmd = osc_cluster.ClusterNodeDel(self.app, None) self.mock_client.remove_nodes_from_cluster = mock.Mock( return_value=self.response) def test_cluster_node_delete(self): arglist = ['-d', 'True', '--nodes', 'node1', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.remove_nodes_from_cluster.assert_called_with( 'my_cluster', ['node1'], destroy_after_deletion=True) def test_cluster_node_delete_without_destroy(self): arglist = ['-d', 'False', '--nodes', 'node1', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.remove_nodes_from_cluster.assert_called_with( 'my_cluster', ['node1'], destroy_after_deletion=False) def test_cluster_node_delete_multi(self): arglist = ['--nodes', 'node1,node2', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.remove_nodes_from_cluster.assert_called_with( 'my_cluster', ['node1', 'node2'], destroy_after_deletion=False) class TestClusterCheck(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterCheck, self).setUp() self.cmd = osc_cluster.CheckCluster(self.app, None) self.mock_client.check_cluster = mock.Mock( return_value=self.response) def test_cluster_check(self): arglist = ['cluster1', 'cluster2', 'cluster3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.check_cluster.assert_has_calls( [mock.call('cluster1'), mock.call('cluster2'), mock.call('cluster3')] ) def test_cluster_check_not_found(self): arglist = ['cluster1'] self.mock_client.check_cluster.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Cluster not found: cluster1', str(error)) class TestClusterRecover(TestCluster): response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} def setUp(self): super(TestClusterRecover, self).setUp() self.cmd = osc_cluster.RecoverCluster(self.app, None) self.mock_client.recover_cluster = mock.Mock( return_value=self.response) def test_cluster_recover(self): arglist = ['cluster1', 'cluster2', 'cluster3', '--check', 'false'] kwargs = {'check': False} parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.recover_cluster.assert_has_calls( [mock.call('cluster1', **kwargs), mock.call('cluster2', **kwargs), mock.call('cluster3', **kwargs)] ) def test_cluster_recover_not_found(self): arglist = ['cluster1'] self.mock_client.recover_cluster.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Cluster not found: cluster1', str(error)) class TestClusterOp(TestCluster): response = {"action": "a3c6d04c-3fca-4e4a-b0b3-c0522ef711f1"} def setUp(self): super(TestClusterOp, self).setUp() self.cmd = osc_cluster.ClusterOp(self.app, None) self.mock_client.perform_operation_on_cluster = mock.Mock( return_value=self.response) def test_cluster_op(self): arglist = ['--operation', 'dance', '--params', 'style=tango', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.perform_operation_on_cluster.assert_called_once_with( 'my_cluster', 'dance', style='tango') def test_cluster_op_not_found(self): arglist = ['--operation', 'dance', 'cluster1'] ex = sdk_exc.ResourceNotFound self.mock_client.perform_operation_on_cluster.side_effect = ex parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Cluster not found: cluster1', str(error)) class TestClusterCollect(TestCluster): response = [ { "node_id": "8bb476c3-0f4c-44ee-9f64-c7b0260814de", "attr_value": "value 1", }, { "node_id": "7d85f602-a948-4a30-afd4-e84f47471c15", "attr_value": "value 2", } ] def setUp(self): super(TestClusterCollect, self).setUp() self.cmd = osc_cluster.ClusterCollect(self.app, None) self.mock_client.collect_cluster_attrs = mock.Mock( return_value=self.response) def test_cluster_collect(self): arglist = ['--path', 'path.to.attr', 'cluster1'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.collect_cluster_attrs.assert_called_once_with( 'cluster1', 'path.to.attr') self.assertEqual(['node_id', 'attr_value'], columns) def test_cluster_collect_with_full_id(self): arglist = ['--path', 'path.to.attr', '--full-id', 'cluster1'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mock_client.collect_cluster_attrs.assert_called_once_with( 'cluster1', 'path.to.attr') self.assertEqual(['node_id', 'attr_value'], columns) class TestClusterRun(TestCluster): attrs = [ mock.Mock(node_id="NODE_ID1", attr_value={"addresses": 'ADDRESS CONTENT 1'}), mock.Mock(node_id="NODE_ID2", attr_value={"addresses": 'ADDRESS CONTENT 2'}) ] def setUp(self): super(TestClusterRun, self).setUp() self.cmd = osc_cluster.ClusterRun(self.app, None) self.mock_client.collect_cluster_attrs = mock.Mock( return_value=self.attrs) @mock.patch('subprocess.Popen') def test__run_script(self, mock_proc): x_proc = mock.Mock(returncode=0) x_stdout = 'OUTPUT' x_stderr = 'ERROR' x_proc.communicate.return_value = (x_stdout, x_stderr) mock_proc.return_value = x_proc addr = { 'private': [ { 'OS-EXT-IPS:type': 'floating', 'version': 4, 'addr': '1.2.3.4', } ] } output = {} self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) mock_proc.assert_called_once_with( ['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4', 'echo foo'], stdout=subprocess.PIPE) self.assertEqual( {'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'}, output) def test__run_script_network_not_found(self): addr = {'foo': 'bar'} output = {} self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) self.assertEqual( {'status': 'FAILED', 'error': "Node 'NODE_ID' is not attached to network 'private'." }, output) def test__run_script_more_than_one_network(self): addr = {'foo': 'bar', 'koo': 'tar'} output = {} self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) self.assertEqual( {'status': 'FAILED', 'error': "Node 'NODE_ID' is attached to more than one " "network. Please pick the network to use."}, output) def test__run_script_no_network(self): addr = {} output = {} self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) self.assertEqual( {'status': 'FAILED', 'error': "Node 'NODE_ID' is not attached to any network."}, output) def test__run_script_no_matching_address(self): addr = { 'private': [ { 'OS-EXT-IPS:type': 'fixed', 'version': 4, 'addr': '1.2.3.4', } ] } output = {} self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) self.assertEqual( {'status': 'FAILED', 'error': "No address that matches network 'private' and " "type 'floating' of IPv4 has been found for node " "'NODE_ID'."}, output) def test__run_script_more_than_one_address(self): addr = { 'private': [ { 'OS-EXT-IPS:type': 'fixed', 'version': 4, 'addr': '1.2.3.4', }, { 'OS-EXT-IPS:type': 'fixed', 'version': 4, 'addr': '5.6.7.8', }, ] } output = {} self.cmd._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john', False, 'identity_path', 'echo foo', '-f bar', output=output) self.assertEqual( {'status': 'FAILED', 'error': "More than one IPv4 fixed address found."}, output) @mock.patch('threading.Thread') @mock.patch.object(osc_cluster.ClusterRun, '_run_script') def test_cluster_run(self, mock_script, mock_thread): arglist = [ '--port', '22', '--address-type', 'fixed', '--network', 'private', '--user', 'root', '--identity-file', 'path-to-identity', '--ssh-options', '-f boo', '--script', 'script-file', 'cluster1' ] parsed_args = self.check_parser(self.cmd, arglist, []) th1 = mock.Mock() th2 = mock.Mock() mock_thread.side_effect = [th1, th2] fake_script = 'blah blah' with mock.patch('senlinclient.v1.cluster.open', mock.mock_open(read_data=fake_script)) as mock_open: self.cmd.take_action(parsed_args) self.mock_client.collect_cluster_attrs.assert_called_once_with( 'cluster1', 'details') mock_open.assert_called_once_with('script-file', 'r') mock_thread.assert_has_calls([ mock.call(target=mock_script, args=('NODE_ID1', 'ADDRESS CONTENT 1', 'private', 'fixed', 22, 'root', False, 'path-to-identity', 'blah blah', '-f boo'), kwargs={'output': {}}), mock.call(target=mock_script, args=('NODE_ID2', 'ADDRESS CONTENT 2', 'private', 'fixed', 22, 'root', False, 'path-to-identity', 'blah blah', '-f boo'), kwargs={'output': {}}) ]) th1.start.assert_called_once_with() th2.start.assert_called_once_with() th1.join.assert_called_once_with() th2.join.assert_called_once_with() python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_client.py0000664000175000017500000004347213643337345025614 0ustar zuulzuul00000000000000# 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. import mock import testtools from senlinclient import plugin from senlinclient.v1 import client @mock.patch.object(plugin, 'create_connection') class ClientTest(testtools.TestCase): def setUp(self): super(ClientTest, self).setUp() self.conn = mock.Mock() self.service = mock.Mock() self.conn.cluster = self.service def test_init_default(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() self.assertEqual(self.conn, sc.conn) self.assertEqual(self.service, sc.service) mock_conn.assert_called_once_with(prof=None, user_agent=None) def test_init_with_params(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client(prof='FOO', user_agent='BAR', zoo='LARR') self.assertEqual(self.conn, sc.conn) self.assertEqual(self.service, sc.service) mock_conn.assert_called_once_with(prof='FOO', user_agent='BAR', zoo='LARR') def test_profile_types(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.profile_types(foo='bar') self.assertEqual(self.service.profile_types.return_value, res) self.service.profile_types.assert_called_once_with(foo='bar') def test_get_profile_type(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_profile_type('FOOBAR') self.assertEqual(self.service.get_profile_type.return_value, res) self.service.get_profile_type.assert_called_once_with('FOOBAR') def test_profiles(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.profiles(foo='bar') self.assertEqual(self.service.profiles.return_value, res) self.service.profiles.assert_called_once_with(foo='bar') def test_get_profile(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_profile('FOOBAR') self.assertEqual(self.service.get_profile.return_value, res) self.service.get_profile.assert_called_once_with('FOOBAR') def test_update_profile(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.update_profile('FAKE_ID', foo='bar') self.assertEqual(self.service.update_profile.return_value, res) self.service.update_profile.assert_called_once_with('FAKE_ID', foo='bar') def test_delete_profile(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_profile('FAKE_ID') self.assertEqual(self.service.delete_profile.return_value, res) self.service.delete_profile.assert_called_once_with( 'FAKE_ID', True) def test_delete_profile_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_profile('FAKE_ID', False) self.assertEqual(self.service.delete_profile.return_value, res) self.service.delete_profile.assert_called_once_with( 'FAKE_ID', False) def test_policy_types(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.policy_types(foo='bar') self.assertEqual(self.service.policy_types.return_value, res) self.service.policy_types.assert_called_once_with(foo='bar') def test_get_policy_type(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_policy_type('FOOBAR') self.assertEqual(self.service.get_policy_type.return_value, res) self.service.get_policy_type.assert_called_once_with('FOOBAR') def test_policies(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.policies(foo='bar') self.assertEqual(self.service.policies.return_value, res) self.service.policies.assert_called_once_with(foo='bar') def test_get_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_policy('FOOBAR') self.assertEqual(self.service.get_policy.return_value, res) self.service.get_policy.assert_called_once_with('FOOBAR') def test_update_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.update_policy('FAKE_ID', foo='bar') self.assertEqual(self.service.update_policy.return_value, res) self.service.update_policy.assert_called_once_with( 'FAKE_ID', foo='bar') def test_delete_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_policy('FAKE_ID') self.assertEqual(self.service.delete_policy.return_value, res) self.service.delete_policy.assert_called_once_with( 'FAKE_ID', True) def test_delete_policy_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_policy('FAKE_ID', False) self.assertEqual(self.service.delete_policy.return_value, res) self.service.delete_policy.assert_called_once_with( 'FAKE_ID', False) def test_clusters(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.clusters(foo='bar') self.assertEqual(self.service.clusters.return_value, res) self.service.clusters.assert_called_once_with(foo='bar') def test_get_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_cluster('FOOBAR') self.assertEqual(self.service.get_cluster.return_value, res) self.service.get_cluster.assert_called_once_with('FOOBAR') def test_create_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.create_cluster(name='FOO', bar='zoo') self.assertEqual(self.service.create_cluster.return_value, res) self.service.create_cluster.assert_called_once_with( name='FOO', bar='zoo') def test_update_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.update_cluster('FAKE_ID', foo='bar') self.assertEqual(self.service.update_cluster.return_value, res) self.service.update_cluster.assert_called_once_with( 'FAKE_ID', foo='bar') def test_delete_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_cluster('FAKE_ID', True) self.assertEqual(self.service.delete_cluster.return_value, res) self.service.delete_cluster.assert_called_once_with( 'FAKE_ID', True, False) def test_delete_cluster_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_cluster('FAKE_ID', True, False) self.assertEqual(self.service.delete_cluster.return_value, res) self.service.delete_cluster.assert_called_once_with( 'FAKE_ID', True, False) def test_cluster_add_nodes(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_add_nodes('FAKE_ID', ['NODE1', 'NODE2']) self.assertEqual(self.service.add_nodes_to_cluster.return_value, res) self.service.add_nodes_to_cluster.assert_called_once_with( 'FAKE_ID', ['NODE1', 'NODE2']) def test_cluster_del_nodes(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_del_nodes('FAKE_ID', ['NODE1', 'NODE2']) self.assertEqual(self.service.remove_nodes_from_cluster.return_value, res) self.service.remove_nodes_from_cluster.assert_called_once_with( 'FAKE_ID', ['NODE1', 'NODE2']) def test_cluster_resize(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_resize('FAKE_ID', foo='bar', zoo=1) self.assertEqual(self.service.resize_cluster.return_value, res) self.service.resize_cluster.assert_called_once_with( 'FAKE_ID', foo='bar', zoo=1) def test_cluster_scale_in(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_scale_in('FAKE_ID', 3) self.assertEqual(self.service.scale_in_cluster.return_value, res) self.service.scale_in_cluster.assert_called_once_with( 'FAKE_ID', 3) def test_cluster_scale_out(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_scale_out('FAKE_ID', 3) self.assertEqual(self.service.scale_out_cluster.return_value, res) self.service.scale_out_cluster.assert_called_once_with( 'FAKE_ID', 3) def test_cluster_policies(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_policies('CLUSTER', foo='bar') self.assertEqual(self.service.cluster_policies.return_value, res) self.service.cluster_policies.assert_called_once_with( 'CLUSTER', foo='bar') def test_get_cluster_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_cluster_policy('PID', 'CID') self.assertEqual(self.service.get_cluster_policy.return_value, res) self.service.get_cluster_policy.assert_called_once_with( 'PID', 'CID') def test_cluster_attach_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_attach_policy('FOO', 'BAR', zoo='car') self.assertEqual(self.service.attach_policy_to_cluster.return_value, res) self.service.attach_policy_to_cluster.assert_called_once_with( 'FOO', 'BAR', zoo='car') def test_cluster_detach_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_detach_policy('FOO', 'BAR') self.assertEqual(self.service.detach_policy_from_cluster.return_value, res) self.service.detach_policy_from_cluster.assert_called_once_with( 'FOO', 'BAR') def test_cluster_update_policy(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.cluster_update_policy('FOO', 'BAR', foo='bar') self.assertEqual(self.service.update_cluster_policy.return_value, res) self.service.update_cluster_policy.assert_called_once_with( 'FOO', 'BAR', foo='bar') def test_check_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.check_cluster('FAKE_CLUSTER_ID') self.assertEqual(self.service.check_cluster.return_value, res) self.service.check_cluster.assert_called_once_with('FAKE_CLUSTER_ID') def test_recover_cluster(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.recover_cluster('FAKE_CLUSTER_ID') self.assertEqual(self.service.recover_cluster.return_value, res) self.service.recover_cluster.assert_called_once_with( 'FAKE_CLUSTER_ID') def test_nodes(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.nodes(foo='bar') self.assertEqual(self.service.nodes.return_value, res) self.service.nodes.assert_called_once_with(foo='bar') def test_get_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_node('FOOBAR') self.assertEqual(self.service.get_node.return_value, res) self.service.get_node.assert_called_once_with('FOOBAR', details=False) def test_get_node_with_details(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_node('FOOBAR', details=True) self.assertEqual(self.service.get_node.return_value, res) self.service.get_node.assert_called_once_with( 'FOOBAR', details=True) def test_create_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.create_node(name='FAKE_NAME', foo='bar') self.assertEqual(self.service.create_node.return_value, res) self.service.create_node.assert_called_once_with( name='FAKE_NAME', foo='bar') def test_update_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.update_node('FAKE_ID', foo='bar') self.assertEqual(self.service.update_node.return_value, res) self.service.update_node.assert_called_once_with( 'FAKE_ID', foo='bar') def test_delete_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_node('FAKE_ID', True) self.assertEqual(self.service.delete_node.return_value, res) self.service.delete_node.assert_called_once_with( 'FAKE_ID', True, False) def test_check_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.check_node('FAKE_ID') self.assertEqual(self.service.check_node.return_value, res) self.service.check_node.assert_called_once_with('FAKE_ID') def test_recover_node(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.recover_node('FAKE_ID') self.assertEqual(self.service.recover_node.return_value, res) self.service.recover_node.assert_called_once_with( 'FAKE_ID') def test_delete_node_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_node('FAKE_ID', True, False) self.assertEqual(self.service.delete_node.return_value, res) self.service.delete_node.assert_called_once_with( 'FAKE_ID', True, False) def test_receivers(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.receivers(foo='bar') self.assertEqual(self.service.receivers.return_value, res) self.service.receivers.assert_called_once_with(foo='bar') def test_get_receiver(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_receiver('FOOBAR') self.assertEqual(self.service.get_receiver.return_value, res) self.service.get_receiver.assert_called_once_with('FOOBAR') def test_create_receiver(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.create_receiver(name='FAKE_NAME', foo='bar') self.assertEqual(self.service.create_receiver.return_value, res) self.service.create_receiver.assert_called_once_with( name='FAKE_NAME', foo='bar') def test_delete_receiver(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_receiver('FAKE_ID') self.assertEqual(self.service.delete_receiver.return_value, res) self.service.delete_receiver.assert_called_once_with( 'FAKE_ID', True) def test_delete_receiver_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.delete_receiver('FAKE_ID', False) self.assertEqual(self.service.delete_receiver.return_value, res) self.service.delete_receiver.assert_called_once_with( 'FAKE_ID', False) def test_actions(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.actions(foo='bar') self.assertEqual(self.service.actions.return_value, res) self.service.actions.assert_called_once_with(foo='bar') def test_get_action(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_action('FOOBAR') self.assertEqual(self.service.get_action.return_value, res) self.service.get_action.assert_called_once_with('FOOBAR') def test_update_action(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.update_action('FAKE_ID', status='CANCELLED') self.assertEqual(self.service.update_action.return_value, res) self.service.update_action.assert_called_once_with( 'FAKE_ID', status='CANCELLED') def test_events(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.events(foo='bar') self.assertEqual(self.service.events.return_value, res) self.service.events.assert_called_once_with(foo='bar') def test_get_event(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() res = sc.get_event('FOOBAR') self.assertEqual(self.service.get_event.return_value, res) self.service.get_event.assert_called_once_with('FOOBAR') python-senlinclient-2.0.1/senlinclient/tests/unit/v1/test_profile_type.py0000664000175000017500000001205213643337345027025 0ustar zuulzuul00000000000000# 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. import mock from openstack import exceptions as sdk_exc from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import profile_type as osc_profile_type class TestProfileType(fakes.TestClusteringv1): def setUp(self): super(TestProfileType, self).setUp() self.mock_client = self.app.client_manager.clustering class TestProfileTypeList(TestProfileType): def setUp(self): super(TestProfileTypeList, self).setUp() self.cmd = osc_profile_type.ProfileTypeList(self.app, None) pt1 = mock.Mock( schema={'foo': 'bar'}, support_status={ "1.0": [{"status": "SUPPORTED", "since": "2016.10"}] } ) pt1.name = "BBB" pt2 = mock.Mock( schema={'foo': 'bar'}, support_status={ "1.0": [{"status": "DEPRECATED", "since": "2016.01"}] } ) pt2.name = "AAA" self.mock_client.profile_types = mock.Mock(return_value=[pt1, pt2]) def test_profile_type_list(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) expected_rows = [ ('AAA', '1.0', 'DEPRECATED since 2016.01'), ('BBB', '1.0', 'SUPPORTED since 2016.10') ] expected_columns = ['name', 'version', 'support_status'] columns, rows = self.cmd.take_action(parsed_args) if len(columns) == 2: expected_columns = ['name', 'version'] expected_rows = [ ('CCC', '1.0') ] self.mock_client.profile_types.assert_called_with() self.assertEqual(expected_columns, columns) self.assertEqual(expected_rows, rows) class TestProfileTypeShow(TestProfileType): def setUp(self): super(TestProfileTypeShow, self).setUp() self.cmd = osc_profile_type.ProfileTypeShow(self.app, None) fake_profile_type = mock.Mock( schema={'foo': 'bar'}, support_status={ "1.0": [{"status": "DEPRECATED", "since": "2016.01"}] } ) fake_profile_type.name = "os.heat.stack-1.0" fake_profile_type.to_dict = mock.Mock(return_value={}) self.mock_client.get_profile_type = mock.Mock( return_value=fake_profile_type) def test_profile_type_show(self): arglist = ['os.heat.stack-1.0'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_profile_type.assert_called_once_with( 'os.heat.stack-1.0') def test_profile_type_show_not_found(self): arglist = ['os.heat.stack-1.1'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.get_profile_type.side_effect = ( sdk_exc.ResourceNotFound()) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Profile Type not found: os.heat.stack-1.1', str(error)) class TestProfileTypeOperations(TestProfileType): def setUp(self): super(TestProfileTypeOperations, self).setUp() self.cmd = osc_profile_type.ProfileTypeOperations(self.app, None) fake_profile_type_ops = mock.Mock( { 'options': { 'abandon': { 'required': False, 'type': 'Map', 'description': 'Abandon a heat stack node.', 'updatable': False } } } ) self.mock_client.list_profile_type_operations = mock.Mock( return_value=fake_profile_type_ops) def test_profile_type_operations(self): arglist = ['os.heat.stack-1.0'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.list_profile_type_operations.assert_called_once_with( 'os.heat.stack-1.0') def test_profile_type_operations_not_found(self): arglist = ['os.heat.stack-1.1'] parsed_args = self.check_parser(self.cmd, arglist, []) self.mock_client.list_profile_type_operations.side_effect = ( sdk_exc.ResourceNotFound()) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Profile Type not found: os.heat.stack-1.1', str(error)) python-senlinclient-2.0.1/senlinclient/client.py0000664000175000017500000000151713643337345022100 0ustar zuulzuul00000000000000# 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 senlinclient.common import utils def Client(api_ver, *args, **kwargs): """Import versioned client module. :param api_ver: API version required. """ module = utils.import_versioned_module(api_ver, 'client') cls = getattr(module, 'Client') return cls(*args, **kwargs) python-senlinclient-2.0.1/senlinclient/common/0000775000175000017500000000000013643337460021532 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/common/__init__.py0000664000175000017500000000000013643337345023633 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/common/format_utils.py0000664000175000017500000000234413643337345024621 0ustar zuulzuul00000000000000# 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 osc_lib.command import command class RawFormat(command.ShowOne): def produce_output(self, parsed_args, column_names, data): if data is None: return self.formatter.emit_one(column_names, data, self.app.stdout, parsed_args) class JsonFormat(RawFormat): @property def formatter_default(self): return 'json' class YamlFormat(RawFormat): @property def formatter_default(self): return 'yaml' class ShellFormat(RawFormat): @property def formatter_default(self): return 'shell' class ValueFormat(RawFormat): @property def formatter_default(self): return 'value' python-senlinclient-2.0.1/senlinclient/common/utils.py0000664000175000017500000001141613643337345023251 0ustar zuulzuul00000000000000# 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 __future__ import print_function from heatclient.common import template_utils from oslo_serialization import jsonutils from oslo_utils import importutils import prettytable import six import yaml from senlinclient.common import exc from senlinclient.common.i18n import _ def import_versioned_module(version, submodule=None): module = 'senlinclient.v%s' % version if submodule: module = '.'.join((module, submodule)) return importutils.import_module(module) def format_nested_dict(d, fields, column_names): if d is None: return '' pt = prettytable.PrettyTable(caching=False, print_empty=False, header=True, field_names=column_names) for n in column_names: pt.align[n] = 'l' keys = sorted(d.keys()) for field in keys: value = d[field] if not isinstance(value, six.string_types): value = jsonutils.dumps(value, indent=2, ensure_ascii=False) if value is None: value = '-' pt.add_row([field, value.strip('"')]) return pt.get_string() def nested_dict_formatter(d, column_names): return lambda o: format_nested_dict(o, d, column_names) def json_formatter(js): return jsonutils.dumps(js, indent=2, ensure_ascii=False) def list_formatter(record): return '\n'.join(record or []) def print_action_result(rid, res): if res[0] == "OK": output = _("accepted by action %s") % res[1] else: output = _("failed due to '%s'") % res[1] print(_(" %(cid)s: %(output)s") % {"cid": rid, "output": output}) def format_parameters(params, parse_semicolon=True): """Reformat parameters into dict of format expected by the API.""" if not params or params == ['{}']: return {} if parse_semicolon: # expect multiple invocations of --parameters but fall back to ';' # delimited if only one --parameters is specified if len(params) == 1: params = params[0].split(';') parameters = {} for p in params: try: (n, v) = p.split(('='), 1) except ValueError: msg = _('Malformed parameter(%s). Use the key=value format.') % p raise exc.CommandError(msg) if n not in parameters: parameters[n] = v else: if not isinstance(parameters[n], list): parameters[n] = [parameters[n]] parameters[n].append(v) return parameters def format_json_parameter(param): '''Return JSON dict from JSON formatted param. :parameter param JSON formatted string :return JSON dict ''' if not param: return {} try: return jsonutils.loads(param) except ValueError: msg = _('Malformed parameter(%s). Use the JSON format.') % param raise exc.CommandError(msg) def get_spec_content(filename): with open(filename, 'r') as f: try: data = yaml.safe_load(f) except Exception as ex: raise exc.CommandError(_('The specified file is not a valid ' 'YAML file: %s') % six.text_type(ex)) return data def process_stack_spec(spec): # Heat stack is a headache, because it demands for client side file # content processing try: tmplfile = spec.get('template', None) except AttributeError as ex: raise exc.FileFormatError(_('The specified file is not a valid ' 'YAML file: %s') % six.text_type(ex)) if not tmplfile: raise exc.FileFormatError(_('No template found in the given ' 'spec file')) tpl_files, template = template_utils.get_template_contents( template_file=tmplfile) env_files, env = template_utils.process_multiple_environments_and_files( env_paths=spec.get('environment', None)) new_spec = { # TODO(Qiming): add context support 'disable_rollback': spec.get('disable_rollback', True), 'context': spec.get('context', {}), 'parameters': spec.get('parameters', {}), 'timeout': spec.get('timeout', 60), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } return new_spec python-senlinclient-2.0.1/senlinclient/common/exc.py0000664000175000017500000001633613643337345022676 0ustar zuulzuul00000000000000# 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 keystoneauth1.exceptions import base as kae_base from keystoneauth1.exceptions import http as kae_http from openstack import exceptions as sdkexc from oslo_serialization import jsonutils from requests import exceptions as reqexc import six from senlinclient.common.i18n import _ verbose = False class BaseException(Exception): """An error occurred.""" def __init__(self, message=None): self.message = message def __str__(self): return self.message or self.__class__.__doc__ class CommandError(BaseException): """Invalid usage of CLI.""" class FileFormatError(BaseException): """Illegal file format detected.""" class HTTPException(BaseException): """Base exception for all HTTP-derived exceptions.""" code = 'N/A' def __init__(self, error=None): super(HTTPException, self).__init__(error) try: self.error = error if 'error' not in self.error: raise KeyError(_('Key "error" not exists')) except KeyError: # If key 'error' does not exist, self.message becomes # no sense. In this case, we return doc of current # exception class instead. self.error = {'error': {'message': self.__class__.__doc__}} except Exception: self.error = {'error': {'message': self.message or self.__class__.__doc__}} def __str__(self): message = self.error['error'].get('message', 'Internal Error') if verbose: traceback = self.error['error'].get('traceback', '') return (_('ERROR: %(message)s\n%(traceback)s') % {'message': message, 'traceback': traceback}) else: code = self.error['error'].get('code', 'Unknown') return _('ERROR(%(code)s): %(message)s') % {'code': code, 'message': message} class ClientError(HTTPException): pass class ServerError(HTTPException): pass class HTTPBadRequest(ClientError): # 400 pass class HTTPUnauthorized(ClientError): # 401 pass class HTTPForbidden(ClientError): # 403 pass class HTTPNotFound(ClientError): # 404 pass class HTTPMethodNotAllowed(ClientError): # 405 pass class HTTPNotAcceptable(ClientError): # 406 pass class HTTPProxyAuthenticationRequired(ClientError): # 407 pass class HTTPRequestTimeout(ClientError): # 408 pass class HTTPConflict(ClientError): # 409 pass class HTTPGone(ClientError): # 410 pass class HTTPLengthRequired(ClientError): # 411 pass class HTTPPreconditionFailed(ClientError): # 412 pass class HTTPRequestEntityTooLarge(ClientError): # 413 pass class HTTPRequestURITooLong(ClientError): # 414 pass class HTTPUnsupportedMediaType(ClientError): # 415 pass class HTTPRequestRangeNotSatisfiable(ClientError): # 416 pass class HTTPExpectationFailed(ClientError): # 417 pass class HTTPInternalServerError(ServerError): # 500 pass class HTTPNotImplemented(ServerError): # 501 pass class HTTPBadGateway(ServerError): # 502 pass class HTTPServiceUnavailable(ServerError): # 503 pass class HTTPGatewayTimeout(ServerError): # 504 pass class HTTPVersionNotSupported(ServerError): # 505 pass class ConnectionRefused(HTTPException): # 111 pass _EXCEPTION_MAP = { 111: ConnectionRefused, 400: HTTPBadRequest, 401: HTTPUnauthorized, 403: HTTPForbidden, 404: HTTPNotFound, 405: HTTPMethodNotAllowed, 406: HTTPNotAcceptable, 407: HTTPProxyAuthenticationRequired, 408: HTTPRequestTimeout, 409: HTTPConflict, 410: HTTPGone, 411: HTTPLengthRequired, 412: HTTPPreconditionFailed, 413: HTTPRequestEntityTooLarge, 414: HTTPRequestURITooLong, 415: HTTPUnsupportedMediaType, 416: HTTPRequestRangeNotSatisfiable, 417: HTTPExpectationFailed, 500: HTTPInternalServerError, 501: HTTPNotImplemented, 502: HTTPBadGateway, 503: HTTPServiceUnavailable, 504: HTTPGatewayTimeout, 505: HTTPVersionNotSupported, } def parse_exception(exc): """Parse exception code and yield useful information. :param exc: details of the exception. """ if isinstance(exc, sdkexc.HttpException): if exc.details is None: data = exc.response.json() code = data.get('code', None) message = data.get('message', None) error = data.get('error', None) if error: record = { 'error': { 'code': exc.http_status, 'message': message or exc.message } } else: info = data.values()[0] record = { 'error': { 'code': info.get('code', code), 'message': info.get('message', message) } } else: try: record = jsonutils.loads(exc.details) except Exception: # If the exc.details is not in JSON format record = { 'error': { 'code': exc.http_status, 'message': exc, } } elif isinstance(exc, reqexc.RequestException): # Exceptions that are not captured by SDK record = { 'error': { 'code': exc.message[1].errno, 'message': exc.message[0], } } elif isinstance(exc, six.string_types): record = jsonutils.loads(exc) # some exception from keystoneauth1 is not shaped by SDK elif isinstance(exc, kae_http.HttpError): record = { 'error': { 'code': exc.http_status, 'message': exc.message } } elif isinstance(exc, kae_base.ClientException): record = { 'error': { # other exceptions from keystoneauth1 is an internal # error to senlin, so set status code to 500 'code': 500, 'message': exc.message } } else: print(_('Unknown exception: %s') % exc) return try: code = record['error']['code'] except KeyError as err: print(_('Malformed exception record, missing field "%s"') % err) print(_('Original error record: %s') % record) return if code in _EXCEPTION_MAP: inst = _EXCEPTION_MAP.get(code) raise inst(record) else: raise HTTPException(record) python-senlinclient-2.0.1/senlinclient/common/i18n.py0000664000175000017500000000147413643337345022673 0ustar zuulzuul00000000000000# 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. """ oslo_i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='senlinclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-senlinclient-2.0.1/senlinclient/v1/0000775000175000017500000000000013643337460020570 5ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/v1/__init__.py0000664000175000017500000000000013643337345022671 0ustar zuulzuul00000000000000python-senlinclient-2.0.1/senlinclient/v1/node.py0000664000175000017500000004614513643337345022103 0ustar zuulzuul00000000000000# 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. """Clustering v1 node action implementations""" import logging import sys from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from oslo_utils import strutils import six from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ListNode(command.Lister): """List nodes.""" log = logging.getLogger(__name__ + ".ListNode") def get_parser(self, prog_name): parser = super(ListNode, self).get_parser(prog_name) parser.add_argument( '--cluster', metavar='', help=_('ID or name of cluster from which nodes are to be listed') ) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned nodes. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter" " keys are: ['status','name']"), action='append' ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc). The valid " "sort keys are:['index', 'name', 'status', 'init_at', " "'created_at', 'updated_at']") ) parser.add_argument( '--limit', metavar='', help=_('Limit the number of nodes returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return nodes that appear after the given node ID') ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Indicate that this node list should include nodes from ' 'all projects. This option is subject to access policy ' 'checking. Default is False') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'name', 'index', 'status', 'cluster_id', 'physical_id', 'profile_name', 'created_at', 'updated_at', 'tainted'] queries = { 'cluster_id': parsed_args.cluster, 'sort': parsed_args.sort, 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) nodes = senlin_client.nodes(**queries) if parsed_args.global_project: columns.append('project_id') if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8], 'cluster_id': lambda x: x[:8] if x else '', 'physical_id': lambda x: x[:8] if x else '' } if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] else: formatters = {} return ( columns, (utils.get_item_properties(n, columns, formatters=formatters) for n in nodes) ) class ShowNode(command.ShowOne): """Show detailed info about the specified node.""" log = logging.getLogger(__name__ + ".ShowNode") def get_parser(self, prog_name): parser = super(ShowNode, self).get_parser(prog_name) parser.add_argument( '--details', default=False, action="store_true", help=_('Include physical object details') ) parser.add_argument( 'node', metavar='', help=_('Name or ID of the node to show the details for') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering return _show_node(senlin_client, parsed_args.node, parsed_args.details) def _show_node(senlin_client, node_id, show_details=False): """Show detailed info about the specified node.""" try: node = senlin_client.get_node(node_id, details=show_details) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % node_id) formatters = { 'metadata': senlin_utils.json_formatter, 'data': senlin_utils.json_formatter, 'dependents': senlin_utils.json_formatter, } data = node.to_dict() if show_details and data['details']: formatters['details'] = senlin_utils.nested_dict_formatter( list(data['details'].keys()), ['property', 'value']) columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class CreateNode(command.ShowOne): """Create the node.""" log = logging.getLogger(__name__ + ".CreateNode") def get_parser(self, prog_name): parser = super(CreateNode, self).get_parser(prog_name) parser.add_argument( '--cluster', metavar='', help=_('Cluster Id or Name for this node') ) parser.add_argument( '--role', metavar='', help=_('Role for this node in the specific cluster') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_('Metadata values to be attached to the node. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon'), action='append' ) parser.add_argument( '--profile', metavar='', required=True, help=_('Profile Id or Name used for this node') ) parser.add_argument( 'name', metavar='', help=_('Name of the node to create') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering attrs = { 'name': parsed_args.name, 'cluster_id': parsed_args.cluster, 'profile_id': parsed_args.profile, 'role': parsed_args.role, 'metadata': senlin_utils.format_parameters(parsed_args.metadata), } node = senlin_client.create_node(**attrs) return _show_node(senlin_client, node.id) class UpdateNode(command.ShowOne): """Update the node.""" log = logging.getLogger(__name__ + ".UpdateNode") def get_parser(self, prog_name): parser = super(UpdateNode, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help=_('New name for the node') ) parser.add_argument( '--profile', metavar='', help=_('ID or name of new profile to use') ) parser.add_argument( '--role', metavar='', help=_('Role for this node in the specific cluster') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_("Metadata values to be attached to the node. " "This can be specified multiple times, or once with " "key-value pairs separated by a semicolon. Use '{}' " "can clean metadata "), action='append' ) parser.add_argument( '--tainted', metavar='', help=_("Whether the node should be marked as tainted. " "If true, this node will be selected first for the next" "cluster scale-in operation.") ) parser.add_argument( 'node', metavar='', help=_('Name or ID of node to update') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering # Find the node first, we need its UUID node = senlin_client.find_node(parsed_args.node) if node is None: raise exc.CommandError(_('Node not found: %s') % parsed_args.node) attrs = { 'name': parsed_args.name, 'role': parsed_args.role, 'profile_id': parsed_args.profile, 'metadata': senlin_utils.format_parameters(parsed_args.metadata), } if parsed_args.tainted is not None: attrs['tainted'] = strutils.bool_from_string( parsed_args.tainted, strict=True, ) senlin_client.update_node(node.id, **attrs) return _show_node(senlin_client, node.id) class DeleteNode(command.Command): """Delete the node(s).""" log = logging.getLogger(__name__ + ".DeleteNode") def get_parser(self, prog_name): parser = super(DeleteNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', nargs='+', help=_('Name or ID of node(s) to delete.') ) parser.add_argument( '--force-delete', action='store_true', help=_('Force to delete node(s).') ) parser.add_argument( '--force', action='store_true', help=_('Skip yes/no prompt (assume yes).') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: if not parsed_args.force and sys.stdin.isatty(): sys.stdout.write( _("Are you sure you want to delete this node(s)" " [y/N]?")) sys.stdout.flush() prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info('Ctrl-c detected.') return except EOFError: # Ctrl-d self.log.info('Ctrl-d detected') return result = {} for nid in parsed_args.node: try: node_delete_action = senlin_client.delete_node( nid, False, parsed_args.force_delete) result[nid] = ('OK', node_delete_action['id']) except Exception as ex: result[nid] = ('ERROR', six.text_type(ex)) for rid, res in result.items(): senlin_utils.print_action_result(rid, res) class CheckNode(command.Command): """Check the node(s).""" log = logging.getLogger(__name__ + ".CheckNode") def get_parser(self, prog_name): parser = super(CheckNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', nargs='+', help=_('ID or name of node(s) to check.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering for nid in parsed_args.node: try: resp = senlin_client.check_node(nid) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % nid) if 'action' in resp: print('Node check request on node %(nid)s is accepted by ' 'action %(action)s.' % {'nid': nid, 'action': resp['action']}) else: print('Request error: %s' % resp) class RecoverNode(command.Command): """Recover the node(s).""" log = logging.getLogger(__name__ + ".RecoverNode") def get_parser(self, prog_name): parser = super(RecoverNode, self).get_parser(prog_name) parser.add_argument( '--check', metavar='', default=False, help=_('Whether the node(s) should check physical resource status ' 'before doing node recover. Default is false') ) parser.add_argument( 'node', metavar='', nargs='+', help=_('ID or name of node(s) to recover.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering params = { 'check': strutils.bool_from_string(parsed_args.check, strict=True) } for nid in parsed_args.node: try: resp = senlin_client.recover_node(nid, **params) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % nid) if 'action' in resp: print('Node recover request on node %(nid)s is accepted by ' 'action %(action)s.' % {'nid': nid, 'action': resp['action']}) else: print('Request error: %s' % resp) class AdoptNode(command.ShowOne): """Adopt (or preview) the node.""" log = logging.getLogger(__name__ + ".AdoptNode") def get_parser(self, prog_name): parser = super(AdoptNode, self).get_parser(prog_name) parser.add_argument( '--identity', metavar='', required=True, help=_('Physical resource id.')) parser.add_argument( '--type', metavar='', required=True, help=_('The name of the profile type.') ) parser.add_argument( '--role', metavar='', help=_('Role for this node in the specific cluster.') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_('Metadata values to be attached to the node. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon.'), action='append' ) parser.add_argument( '--name', metavar='', help=_('Name of the node to adopt.') ) parser.add_argument( '--overrides', metavar='', help=_('JSON formatted specification for overriding this node ' 'properties.') ) parser.add_argument( '--preview', default=False, help=_('Whether preview the node adopt request. If set, ' 'only previewing this node and do not adopt.'), action='store_true', ) parser.add_argument( '--snapshot', default=False, help=_('Whether a shapshot of the existing physical object ' 'should be created before the object is adopted as ' 'a node.'), action='store_true' ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering preview = True if parsed_args.preview else False attrs = { 'identity': parsed_args.identity, 'overrides': senlin_utils.format_json_parameter( parsed_args.overrides), 'snapshot': parsed_args.snapshot, 'type': parsed_args.type } if not preview: attrs.update({ 'name': parsed_args.name, 'role': parsed_args.role, 'metadata': senlin_utils.format_parameters( parsed_args.metadata), }) node = senlin_client.adopt_node(preview, **attrs) if not preview: return _show_node(senlin_client, node.id) else: formatters = {} formatters['node_preview'] = senlin_utils.nested_dict_formatter( ['type', 'version', 'properties'], ['property', 'value']) data = node['node_profile'] columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class NodeOp(command.Lister): """Perform an operation on a node.""" log = logging.getLogger(__name__ + ".NodeOp") def get_parser(self, prog_name): parser = super(NodeOp, self).get_parser(prog_name) parser.add_argument( '--operation', metavar='', required=True, help=_('Operation to be performed on the node.') ) parser.add_argument( '--params', metavar='', help=_("Parameters to for the specified operation. " "This can be specified multiple times, or once with " "parameters separated by a semicolon."), action="append" ) parser.add_argument( 'node', metavar='', help=_('ID or name the node operate on.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering nid = parsed_args.node if parsed_args.params: params = senlin_utils.format_parameters(parsed_args.params) else: params = {} try: resp = senlin_client.perform_operation_on_node( nid, parsed_args.operation, **params) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % nid) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) python-senlinclient-2.0.1/senlinclient/v1/receiver.py0000664000175000017500000002612013643337345022751 0ustar zuulzuul00000000000000# 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. """Clustering v1 receiver action implementations""" import logging import sys from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ListReceiver(command.Lister): """List receivers that meet the criteria.""" log = logging.getLogger(__name__ + ".ListReceiver") def get_parser(self, prog_name): parser = super(ListReceiver, self).get_parser(prog_name) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned receivers. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter " "keys are: ['name', 'type', 'action', 'cluster_id', " "'user_id']"), action='append' ) parser.add_argument( '--limit', metavar='', help=_('Limit the number of receivers returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return receivers that appear after the given ' 'receiver ID') ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc). The valid " "sort keys are: ['name', 'type', 'action', 'cluster_id', " "'created_at']") ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Indicate that the list should include receivers from' ' all projects. This option is subject to access policy ' 'checking. Default is False') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'name', 'type', 'cluster_id', 'action', 'created_at'] queries = { 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'sort': parsed_args.sort, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) receivers = senlin_client.receivers(**queries) formatters = {} if parsed_args.global_project: columns.append('project_id') columns.append('user_id') if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8], 'cluster_id': lambda x: x[:8] if x else None, } if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] formatters['user_id'] = lambda x: x[:8] return ( columns, (utils.get_item_properties(r, columns, formatters=formatters) for r in receivers) ) class ShowReceiver(command.ShowOne): """Show the receiver details.""" log = logging.getLogger(__name__ + ".ShowReceiver") def get_parser(self, prog_name): parser = super(ShowReceiver, self).get_parser(prog_name) parser.add_argument( 'receiver', metavar='', help=_('Name or ID of the receiver to show') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering return _show_receiver(senlin_client, parsed_args.receiver) def _show_receiver(senlin_client, receiver_id): try: receiver = senlin_client.get_receiver(receiver_id) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Receiver not found: %s') % receiver_id) formatters = { 'actor': senlin_utils.json_formatter, 'params': senlin_utils.json_formatter, 'channel': senlin_utils.json_formatter, } data = receiver.to_dict() columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class CreateReceiver(command.ShowOne): """Create a receiver.""" log = logging.getLogger(__name__ + ".CreateReceiver") def get_parser(self, prog_name): parser = super(CreateReceiver, self).get_parser(prog_name) parser.add_argument( '--type', metavar='', default='webhook', help=_('Type of the receiver to create. Receiver type can be ' '"webhook" or "message". Default to "webhook".') ) parser.add_argument( '--params', metavar='<"key1=value1;key2=value2...">', help=_('A dictionary of parameters that will be passed to target ' 'action when the receiver is triggered'), action='append' ) parser.add_argument( '--cluster', metavar='', help=_('Targeted cluster for this receiver. Required if ' 'receiver type is webhook') ) parser.add_argument( '--action', metavar='', help=_('Name or ID of the targeted action to be triggered. ' 'Required if receiver type is webhook') ) parser.add_argument( 'name', metavar='', help=_('Name of the receiver to create') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) if parsed_args.type == 'webhook': if (not parsed_args.cluster or not parsed_args.action): msg = _('cluster and action parameters are required to create ' 'webhook type of receiver.') raise exc.CommandError(msg) senlin_client = self.app.client_manager.clustering params = { 'name': parsed_args.name, 'type': parsed_args.type, 'cluster_id': parsed_args.cluster, 'action': parsed_args.action, 'params': senlin_utils.format_parameters(parsed_args.params) } receiver = senlin_client.create_receiver(**params) return _show_receiver(senlin_client, receiver.id) class UpdateReceiver(command.ShowOne): """Update a receiver.""" log = logging.getLogger(__name__ + ".UpdateReceiver") def get_parser(self, prog_name): parser = super(UpdateReceiver, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help=_('Name of the receiver to create') ) parser.add_argument( '--action', metavar='', help=_('Name or ID of the targeted action to be triggered. ' 'Required if receiver type is webhook') ) parser.add_argument( '--params', metavar='<"key1=value1;key2=value2...">', help=_('A dictionary of parameters that will be passed to target ' 'action when the receiver is triggered'), action='append' ) parser.add_argument( 'receiver', metavar='', help=_('Name or ID of receiver(s) to update') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering params = { 'name': parsed_args.name, 'params': senlin_utils.format_parameters(parsed_args.params) } if parsed_args.action: params['action'] = parsed_args.action receiver = senlin_client.find_receiver(parsed_args.receiver) if receiver is None: raise exc.CommandError(_('Receiver not found: %s') % parsed_args.receiver) senlin_client.update_receiver(receiver.id, **params) return _show_receiver(senlin_client, receiver_id=receiver.id) class DeleteReceiver(command.Command): """Delete receiver(s).""" log = logging.getLogger(__name__ + ".DeleteReceiver") def get_parser(self, prog_name): parser = super(DeleteReceiver, self).get_parser(prog_name) parser.add_argument( 'receiver', metavar='', nargs='+', help=_('Name or ID of receiver(s) to delete') ) parser.add_argument( '--force', action='store_true', help=_('Skip yes/no prompt (assume yes)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: if not parsed_args.force and sys.stdin.isatty(): sys.stdout.write( _("Are you sure you want to delete this receiver(s)" " [y/N]?")) sys.stdout.flush() prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info('Ctrl-c detected.') return except EOFError: # Ctrl-d self.log.info('Ctrl-d detected') return failure_count = 0 for rid in parsed_args.receiver: try: senlin_client.delete_receiver(rid, False) except Exception as ex: failure_count += 1 print(ex) if failure_count: raise exc.CommandError(_('Failed to delete %(count)s of the ' '%(total)s specified receiver(s).') % {'count': failure_count, 'total': len(parsed_args.receiver)}) print('Receiver deleted: %s' % parsed_args.receiver) python-senlinclient-2.0.1/senlinclient/v1/client.py0000664000175000017500000003672313643337345022435 0ustar zuulzuul00000000000000# 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 openstack import exceptions from senlinclient.common import exc from senlinclient import plugin class Client(object): def __init__(self, prof=None, user_agent=None, **kwargs): try: conn = plugin.create_connection(prof=prof, user_agent=user_agent, **kwargs) except exceptions.HttpException as ex: exc.parse_exception(ex.details) self.conn = conn self.service = self.conn.cluster ###################################################################### # The following operations are interfaces exposed to other software # which invokes senlinclient today. # These methods form a temporary translation layer. This layer will be # useless when OpenStackSDK has been adopted all senlin resources. ###################################################################### def profile_types(self, **query): """List profile types Doc link: https://docs.openstack.org/api-ref/clustering/#list-profile-types """ return self.service.profile_types(**query) def get_profile_type(self, profile_type): """Show profile type details Doc link: https://docs.openstack.org/api-ref/clustering/ #show-profile-type-details """ return self.service.get_profile_type(profile_type) def list_profile_type_operations(self, profile_type): """Show profile type operations Doc link: https://docs.openstack.org/api-ref/clustering/ #show-profile-type-details """ return self.service.list_profile_type_operations(profile_type) def profiles(self, **query): """List profiles Doc link: https://docs.openstack.org/api-ref/clustering/#list-profiles """ return self.service.profiles(**query) def create_profile(self, **attrs): """Create a profile Doc link: https://docs.openstack.org/api-ref/clustering/#create-profile """ return self.service.create_profile(**attrs) def get_profile(self, profile): """Show profile details Doc link: https://docs.openstack.org/api-ref/clustering/#show-profile-details """ return self.service.get_profile(profile) def update_profile(self, profile, **attrs): """Update a profile Doc link: https://docs.openstack.org/api-ref/clustering/#update-profile """ return self.service.update_profile(profile, **attrs) def delete_profile(self, profile, ignore_missing=True): """Delete a profile Doc link: https://docs.openstack.org/api-ref/clustering/#delete-profile """ return self.service.delete_profile(profile, ignore_missing) def validate_profile(self, **attrs): """Validate a profile spec Doc link: https://docs.openstack.org/api-ref/clustering/#validate-profile """ return self.service.validate_profile(**attrs) def policy_types(self, **query): """List policy types Doc link: https://docs.openstack.org/api-ref/clustering/#list-policy-types """ return self.service.policy_types(**query) def get_policy_type(self, policy_type): """Show policy type details Doc link: https://docs.openstack.org/api-ref/clustering/ #show-policy-type-details """ return self.service.get_policy_type(policy_type) def policies(self, **query): """List policies Doc link: https://docs.openstack.org/api-ref/clustering/#list-policies """ return self.service.policies(**query) def create_policy(self, **attrs): """Create a policy Doc link: https://docs.openstack.org/api-ref/clustering/#create-policy """ return self.service.create_policy(**attrs) def get_policy(self, policy): """Show policy details Doc link: https://docs.openstack.org/api-ref/clustering/#show-policy-details """ return self.service.get_policy(policy) def update_policy(self, policy, **attrs): """Update policy Doc link: https://docs.openstack.org/api-ref/clustering/#update-policy """ return self.service.update_policy(policy, **attrs) def delete_policy(self, policy, ignore_missing=True): """Delete policy Doc link: https://docs.openstack.org/api-ref/clustering/#delete-policy """ return self.service.delete_policy(policy, ignore_missing) def validate_policy(self, **attrs): """validate a policy spec Doc link: https://docs.openstack.org/api-ref/clustering/#validate-policy """ return self.service.validate_policy(**attrs) def clusters(self, **queries): """List clusters Doc link: https://docs.openstack.org/api-ref/clustering/#list-clusters """ return self.service.clusters(**queries) def create_cluster(self, **attrs): """Create a cluster Doc link: https://docs.openstack.org/api-ref/clustering/#create-cluster """ return self.service.create_cluster(**attrs) def get_cluster(self, cluster): """Show cluster details Doc link: https://docs.openstack.org/api-ref/clustering/#show-cluster-details """ return self.service.get_cluster(cluster) def update_cluster(self, cluster, **attrs): """Update cluster Doc link: https://docs.openstack.org/api-ref/clustering/#update-cluster """ return self.service.update_cluster(cluster, **attrs) def delete_cluster(self, cluster, ignore_missing=True, force_delete=False): """Delete cluster Doc link: https://docs.openstack.org/api-ref/clustering/#delete-cluster """ return self.service.delete_cluster(cluster, ignore_missing, force_delete) def cluster_add_nodes(self, cluster, nodes): """Add a node to cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #add-nodes-to-a-cluster """ return self.service.add_nodes_to_cluster(cluster, nodes) def cluster_del_nodes(self, cluster, nodes): """Delete a node belongs to cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #remove-nodes-from-a-cluster """ return self.service.remove_nodes_from_cluster(cluster, nodes) def cluster_replace_nodes(self, cluster, nodes): """Replace the nodes in a cluster with specified nodes Doc link: https://docs.openstack.org/api-ref/clustering/ #replace-nodes-in-a-cluster """ return self.service.replace_nodes_in_cluster(cluster, nodes) def cluster_resize(self, cluster, **params): """Resize cluster Doc link: https://docs.openstack.org/api-ref/clustering/#resize-a-cluster """ return self.service.resize_cluster(cluster, **params) def cluster_scale_out(self, cluster, count): """Scale out cluster Doc link: https://docs.openstack.org/api-ref/clustering/#scale-out-a-cluster """ return self.service.scale_out_cluster(cluster, count) def cluster_scale_in(self, cluster, count): """Scale in cluster Doc link: https://docs.openstack.org/api-ref/clustering/#scale-in-a-cluster """ return self.service.scale_in_cluster(cluster, count) def cluster_policies(self, cluster, **queries): """List all policies attached to cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #list-all-cluster-policies """ return self.service.cluster_policies(cluster, **queries) def get_cluster_policy(self, policy, cluster): """Show details of a policy attached to cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #show-cluster-policy-details """ return self.service.get_cluster_policy(policy, cluster) def cluster_attach_policy(self, cluster, policy, **attrs): """Attach a policy to cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #attach-a-policy-to-a-cluster """ return self.service.attach_policy_to_cluster(cluster, policy, **attrs) def cluster_detach_policy(self, cluster, policy): """Detach a policy from cluster Doc link: https://docs.openstack.org/api-ref/clustering/ #detach-a-policy-from-a-cluster """ return self.service.detach_policy_from_cluster(cluster, policy) def cluster_update_policy(self, cluster, policy, **attrs): """Update the policy attachment Doc link: https://docs.openstack.org/api-ref/clustering/ #update-a-policy-on-a-cluster """ return self.service.update_cluster_policy(cluster, policy, **attrs) def collect_cluster_attrs(self, cluster, path): """Collect cluster attributes Doc link: https://docs.openstack.org/api-ref/clustering/ #collect-attributes-across-a-cluster """ return self.service.collect_cluster_attrs(cluster, path) def check_cluster(self, cluster, **params): """Check cluster's health status Doc link: https://docs.openstack.org/api-ref/clustering/ #check-a-cluster-s-health-status """ return self.service.check_cluster(cluster, **params) def recover_cluster(self, cluster, **params): """Recover cluster from failure state Doc link: https://docs.openstack.org/api-ref/clustering/ #recover-a-cluster-to-a-healthy-status """ return self.service.recover_cluster(cluster, **params) def perform_operation_on_cluster(self, cluster, operation, **params): """Perform an operation on a cluster. Doc link: https://docs.openstack.org/api-ref/clustering/ #perform-an-operation-on-a-cluster """ return self.service.perform_operation_on_cluster(cluster, operation, **params) def nodes(self, **queries): """List nodes Doc link: https://docs.openstack.org/api-ref/clustering/#list-nodes """ return self.service.nodes(**queries) def create_node(self, **attrs): """Create a node Doc link: https://docs.openstack.org/api-ref/clustering/#create-node """ return self.service.create_node(**attrs) def adopt_node(self, preview=False, **attrs): """Adopt a node Doc link: https://docs.openstack.org/api-ref/clustering/#adopt-node https://docs.openstack.org/api-ref/clustering/#adopt-node-preview """ return self.service.adopt_node(preview, **attrs) def get_node(self, node, details=False): """Show node details Doc link: https://docs.openstack.org/api-ref/clustering/#show-node-details """ return self.service.get_node(node, details=details) def update_node(self, node, **attrs): """Update node Doc link: https://docs.openstack.org/api-ref/clustering/#update-node """ return self.service.update_node(node, **attrs) def delete_node(self, node, ignore_missing=True, force_delete=False): """Delete node Doc link: https://docs.openstack.org/api-ref/clustering/#delete-node """ return self.service.delete_node(node, ignore_missing, force_delete) def check_node(self, node, **params): """Check node's health status Doc link: https://docs.openstack.org/api-ref/clustering/#check-a-node-s-health """ return self.service.check_node(node, **params) def recover_node(self, node, **params): """Recover node from failure state Doc link: https://docs.openstack.org/api-ref/clustering/ #recover-a-node-to-healthy-status """ return self.service.recover_node(node, **params) def perform_operation_on_node(self, node, operation, **params): """Perform an operation on a node. Doc link: https://docs.openstack.org/api-ref/clustering/ #perform-an-operation-on-a-node """ return self.service.perform_operation_on_node(node, operation, **params) def receivers(self, **queries): """List receivers Doc link: https://docs.openstack.org/api-ref/clustering/#list-receivers """ return self.service.receivers(**queries) def create_receiver(self, **attrs): """Creare a receiver Doc link: https://docs.openstack.org/api-ref/clustering/#create-receiver """ return self.service.create_receiver(**attrs) def get_receiver(self, receiver): """Show receiver details Doc link: https://docs.openstack.org/api-ref/clustering/#show-receiver-details """ return self.service.get_receiver(receiver) def update_receiver(self, receiver, **attrs): """Update receiver Doc link: https://docs.openstack.org/api-ref-clustering-v1.html#updateReceiver """ return self.service.update_receiver(receiver, **attrs) def delete_receiver(self, receiver, ignore_missing=True): """Delete receiver Doc link: https://docs.openstack.org/api-ref/clustering/#delete-receiver """ return self.service.delete_receiver(receiver, ignore_missing) def events(self, **queries): """List events Doc link: https://docs.openstack.org/api-ref/clustering/#list-events """ return self.service.events(**queries) def get_event(self, event): """Show event details Doc link: https://docs.openstack.org/api-ref/clustering/#shows-event-details """ return self.service.get_event(event) def actions(self, **queries): """List actions Doc link: https://docs.openstack.org/api-ref/clustering/#list-actions """ return self.service.actions(**queries) def get_action(self, action): """Show action details Doc link: https://docs.openstack.org/api-ref/clustering/#show-action-details """ return self.service.get_action(action) def update_action(self, action, **attrs): """Update an action Doc link: https://docs.openstack.org/api-ref/clustering/#update-action """ return self.service.update_action(action, **attrs) def services(self, **queries): """List services Doc link: https://docs.openstack.org/api-ref/clustering/#list-services """ return self.service.services(**queries) python-senlinclient-2.0.1/senlinclient/v1/event.py0000664000175000017500000001203713643337345022270 0ustar zuulzuul00000000000000# 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. """Clustering v1 event action implementations""" import logging from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ListEvent(command.Lister): """List events.""" log = logging.getLogger(__name__ + ".ListEvent") def get_parser(self, prog_name): parser = super(ListEvent, self).get_parser(prog_name) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned events. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter " "keys are: ['level', 'otype', 'oid' ,'cluster_id', " "'oname', 'action']. " "NOTICE: The value of 'oid' or 'cluster_id', " "if provided, must be a full ID."), action='append' ) parser.add_argument( '--limit', metavar='', help=_('Limit the number of events returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return events that appear after the given event ID') ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc). The valid " "sort keys are: ['timestamp', 'level', 'oid', 'otype', " "'oname', 'action', 'status']") ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Whether events from all projects should be listed. ' ' Default to False. Setting this to True may demand ' 'for an admin privilege') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'generated_at', 'obj_type', 'obj_id', 'obj_name', 'action', 'status', 'level', 'cluster_id', 'meta_data'] queries = { 'sort': parsed_args.sort, 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) formatters = {} if parsed_args.global_project: columns.append('project_id') if not parsed_args.full_id: formatters['id'] = lambda x: x[:8] formatters['obj_id'] = lambda x: x[:8] if x else '' if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] formatters['cluster_id'] = lambda x: x[:8] if x else '' events = senlin_client.events(**queries) return (columns, (utils.get_item_properties(e, columns, formatters=formatters) for e in events)) class ShowEvent(command.ShowOne): """Show the event details.""" log = logging.getLogger(__name__ + ".ShowEvent") def get_parser(self, prog_name): parser = super(ShowEvent, self).get_parser(prog_name) parser.add_argument( 'event', metavar='', help=_('ID of event to display details for') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: event = senlin_client.get_event(parsed_args.event) except sdk_exc.ResourceNotFound: raise exc.CommandError(_("Event not found: %s") % parsed_args.event) data = event.to_dict() columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns) python-senlinclient-2.0.1/senlinclient/v1/policy.py0000664000175000017500000002405013643337345022444 0ustar zuulzuul00000000000000# 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. """Clustering v1 policy action implementations""" import logging import sys from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ListPolicy(command.Lister): """List policies that meet the criteria.""" log = logging.getLogger(__name__ + ".ListPolicy") def get_parser(self, prog_name): parser = super(ListPolicy, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', help=_('Limit the number of policies returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return policies that appear after the given policy ' 'ID') ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc). The valid " "sort keys are: ['type', 'name', 'created_at', " "'updated_at']") ) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned policies. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter " "keys are: ['type', 'name']"), action='append' ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Indicate that the list should include policies from' ' all projects. This option is subject to access policy ' 'checking. Default is False') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'name', 'type', 'created_at'] queries = { 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'sort': parsed_args.sort, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) policies = senlin_client.policies(**queries) formatters = {} if parsed_args.global_project: columns.append('project_id') if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8] } if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] return ( columns, (utils.get_item_properties(p, columns, formatters=formatters) for p in policies) ) class ShowPolicy(command.ShowOne): """Show the policy details.""" log = logging.getLogger(__name__ + ".ShowPolicy") def get_parser(self, prog_name): parser = super(ShowPolicy, self).get_parser(prog_name) parser.add_argument( 'policy', metavar='', help=_('Name or Id of the policy to show') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering return _show_policy(senlin_client, policy_id=parsed_args.policy) def _show_policy(senlin_client, policy_id): try: policy = senlin_client.get_policy(policy_id) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Policy not found: %s') % policy_id) formatters = { 'spec': senlin_utils.json_formatter } data = policy.to_dict() columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class CreatePolicy(command.ShowOne): """Create a policy.""" log = logging.getLogger(__name__ + ".CreatePolicy") def get_parser(self, prog_name): parser = super(CreatePolicy, self).get_parser(prog_name) parser.add_argument( '--spec-file', metavar='', required=True, help=_('The spec file used to create the policy') ) parser.add_argument( 'name', metavar='', help=_('Name of the policy to create') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering spec = senlin_utils.get_spec_content(parsed_args.spec_file) attrs = { 'name': parsed_args.name, 'spec': spec, } policy = senlin_client.create_policy(**attrs) return _show_policy(senlin_client, policy.id) class UpdatePolicy(command.ShowOne): """Update a policy.""" log = logging.getLogger(__name__ + ".UpdatePolicy") def get_parser(self, prog_name): parser = super(UpdatePolicy, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help=_('New name of the policy to be updated') ) parser.add_argument( 'policy', metavar='', help=_('Name or ID of the policy to be updated') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering params = { 'name': parsed_args.name, } policy = senlin_client.find_policy(parsed_args.policy) if policy is None: raise exc.CommandError(_('Policy not found: %s') % parsed_args.policy) senlin_client.update_policy(policy.id, **params) return _show_policy(senlin_client, policy_id=policy.id) class DeletePolicy(command.Command): """Delete policy(s).""" log = logging.getLogger(__name__ + ".DeletePolicy") def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) parser.add_argument( 'policy', metavar='', nargs='+', help=_('Name or ID of policy(s) to delete') ) parser.add_argument( '--force', action='store_true', help=_('Skip yes/no prompt (assume yes)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: if not parsed_args.force and sys.stdin.isatty(): sys.stdout.write( _("Are you sure you want to delete this policy(s)" " [y/N]?")) sys.stdout.flush() prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info('Ctrl-c detected.') return except EOFError: # Ctrl-d self.log.info('Ctrl-d detected') return failure_count = 0 for pid in parsed_args.policy: try: senlin_client.delete_policy(pid, False) except Exception as ex: failure_count += 1 print(ex) if failure_count: raise exc.CommandError(_('Failed to delete %(count)s of the ' '%(total)s specified policy(s).') % {'count': failure_count, 'total': len(parsed_args.policy)}) print('Policy deleted: %s' % parsed_args.policy) class ValidatePolicy(command.ShowOne): """Validate a policy.""" log = logging.getLogger(__name__ + ".ValidatePolicy") def get_parser(self, prog_name): parser = super(ValidatePolicy, self).get_parser(prog_name) parser.add_argument( '--spec-file', metavar='', required=True, help=_('The spec file of the policy to be validated') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering spec = senlin_utils.get_spec_content(parsed_args.spec_file) attrs = { 'spec': spec, } policy = senlin_client.validate_policy(**attrs) formatters = { 'spec': senlin_utils.json_formatter } columns = [ 'created_at', 'data', 'domain', 'id', 'name', 'project', 'spec', 'type', 'updated_at', 'user' ] return columns, utils.get_dict_properties(policy.to_dict(), columns, formatters=formatters) python-senlinclient-2.0.1/senlinclient/v1/profile.py0000664000175000017500000003123713643337345022612 0ustar zuulzuul00000000000000# 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. """Clustering v1 profile action implementations""" import logging import sys from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ShowProfile(command.ShowOne): """Show profile details.""" log = logging.getLogger(__name__ + ".ShowProfile") def get_parser(self, prog_name): parser = super(ShowProfile, self).get_parser(prog_name) parser.add_argument( 'profile', metavar='', help='Name or ID of profile to show', ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering return _show_profile(senlin_client, profile_id=parsed_args.profile) def _show_profile(senlin_client, profile_id): try: data = senlin_client.get_profile(profile_id) except sdk_exc.ResourceNotFound: raise exc.CommandError('Profile not found: %s' % profile_id) else: formatters = {} formatters['metadata'] = senlin_utils.json_formatter formatters['spec'] = senlin_utils.nested_dict_formatter( ['type', 'version', 'properties'], ['property', 'value']) data = data.to_dict() columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class ListProfile(command.Lister): """List profiles that meet the criteria.""" log = logging.getLogger(__name__ + ".ListProfile") def get_parser(self, prog_name): parser = super(ListProfile, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', help=_('Limit the number of profiles returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return profiles that appear after the given profile ' 'ID') ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of keys" " separated by commas. Each key can be optionally appended " "by a sort direction (:asc or :desc). The valid sort_keys " "are:['type', 'name', 'created_at', 'updated_at']") ) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned profiles. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter " "keys are: ['type', 'name']"), action='append' ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Indicate that the list should include profiles from' ' all projects. This option is subject to access policy ' 'checking. Default is False') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'name', 'type', 'created_at'] queries = { 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'sort': parsed_args.sort, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) data = senlin_client.profiles(**queries) formatters = {} if parsed_args.global_project: columns.append('project_id') if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8], } if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] return ( columns, (utils.get_item_properties(p, columns, formatters=formatters) for p in data) ) class DeleteProfile(command.Command): """Delete profile(s).""" log = logging.getLogger(__name__ + ".DeleteProfile") def get_parser(self, prog_name): parser = super(DeleteProfile, self).get_parser(prog_name) parser.add_argument( 'profile', metavar='', nargs='+', help=_('Name or ID of profile(s) to delete') ) parser.add_argument( '--force', action='store_true', help=_('Skip yes/no prompt (assume yes)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: if not parsed_args.force and sys.stdin.isatty(): sys.stdout.write( _("Are you sure you want to delete this profile(s)" " [y/N]?")) sys.stdout.flush() prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info('Ctrl-c detected.') return except EOFError: # Ctrl-d self.log.info('Ctrl-d detected') return failure_count = 0 for pid in parsed_args.profile: try: senlin_client.delete_profile(pid, False) except Exception as ex: failure_count += 1 print(ex) if failure_count: raise exc.CommandError(_('Failed to delete %(count)s of the ' '%(total)s specified profile(s).') % {'count': failure_count, 'total': len(parsed_args.profile)}) print('Profile deleted: %s' % parsed_args.profile) class CreateProfile(command.ShowOne): """Create a profile.""" log = logging.getLogger(__name__ + ".CreateProfile") def get_parser(self, prog_name): parser = super(CreateProfile, self).get_parser(prog_name) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_('Metadata values to be attached to the profile. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon'), action='append' ) parser.add_argument( '--spec-file', metavar='', required=True, help=_('The spec file used to create the profile') ) parser.add_argument( 'name', metavar='', help=_('Name of the profile to create') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering spec = senlin_utils.get_spec_content(parsed_args.spec_file) type_name = spec.get('type', None) type_version = spec.get('version', None) properties = spec.get('properties', None) if type_name is None: raise exc.CommandError(_("Missing 'type' key in spec file.")) if type_version is None: raise exc.CommandError(_("Missing 'version' key in spec file.")) if properties is None: raise exc.CommandError(_("Missing 'properties' key in spec file.")) if type_name == 'os.heat.stack': stack_properties = senlin_utils.process_stack_spec(properties) spec['properties'] = stack_properties params = { 'name': parsed_args.name, 'spec': spec, 'metadata': senlin_utils.format_parameters(parsed_args.metadata), } profile = senlin_client.create_profile(**params) return _show_profile(senlin_client, profile_id=profile.id) class UpdateProfile(command.ShowOne): """Update a profile.""" log = logging.getLogger(__name__ + ".UpdateProfile") def get_parser(self, prog_name): parser = super(UpdateProfile, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help=_('The new name for the profile') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_("Metadata values to be attached to the profile. " "This can be specified multiple times, or once with " "key-value pairs separated by a semicolon. Use '{}' " "can clean metadata "), action='append' ) parser.add_argument( 'profile', metavar='', help=_('Name or ID of the profile to update') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering params = { 'name': parsed_args.name, } if parsed_args.metadata: params['metadata'] = senlin_utils.format_parameters( parsed_args.metadata) # Find the profile first, we need its id profile = senlin_client.find_profile(parsed_args.profile) if profile is None: raise exc.CommandError(_('Profile not found: %s') % parsed_args.profile) senlin_client.update_profile(profile.id, **params) return _show_profile(senlin_client, profile_id=profile.id) class ValidateProfile(command.ShowOne): """Validate a profile.""" log = logging.getLogger(__name__ + ".ValidateProfile") def get_parser(self, prog_name): parser = super(ValidateProfile, self).get_parser(prog_name) parser.add_argument( '--spec-file', metavar='', required=True, help=_('The spec file of the profile to be validated') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering spec = senlin_utils.get_spec_content(parsed_args.spec_file) type_name = spec.get('type', None) type_version = spec.get('version', None) properties = spec.get('properties', None) if type_name is None: raise exc.CommandError(_("Missing 'type' key in spec file.")) if type_version is None: raise exc.CommandError(_("Missing 'version' key in spec file.")) if properties is None: raise exc.CommandError(_("Missing 'properties' key in spec file.")) if type_name == 'os.heat.stack': stack_properties = senlin_utils.process_stack_spec(properties) spec['properties'] = stack_properties params = { 'spec': spec, } profile = senlin_client.validate_profile(**params) formatters = {} formatters['metadata'] = senlin_utils.json_formatter formatters['spec'] = senlin_utils.nested_dict_formatter( ['type', 'version', 'properties'], ['property', 'value']) columns = [ 'created_at', 'domain', 'id', 'metadata', 'name', 'project_id', 'spec', 'type', 'updated_at', 'user_id' ] return columns, utils.get_dict_properties(profile.to_dict(), columns, formatters=formatters) python-senlinclient-2.0.1/senlinclient/v1/cluster.py0000664000175000017500000012545713643337345022643 0ustar zuulzuul00000000000000# 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. """Clustering v1 cluster action implementations""" import logging import subprocess import sys import threading import time from openstack import exceptions as sdk_exc from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from oslo_utils import strutils import six from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils class ListCluster(command.Lister): """List clusters.""" log = logging.getLogger(__name__ + ".ListCluster") def get_parser(self, prog_name): parser = super(ListCluster, self).get_parser(prog_name) parser.add_argument( '--filters', metavar='<"key1=value1;key2=value2...">', help=_("Filter parameters to apply on returned clusters. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter" " keys are: ['status', 'name']"), action='append' ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc). The valid " "sort keys are: ['name', 'status', 'init_at', " "'created_at', 'updated_at']")) parser.add_argument( '--limit', metavar='', help=_('Limit the number of clusters returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return clusters that appear after the given cluster ' 'ID') ) parser.add_argument( '--global-project', default=False, action="store_true", help=_('Indicate that the cluster list should include clusters ' 'from all projects. This option is subject to access ' 'policy checking. Default is False') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering columns = ['id', 'name', 'status', 'created_at', 'updated_at'] queries = { 'limit': parsed_args.limit, 'marker': parsed_args.marker, 'sort': parsed_args.sort, 'global_project': parsed_args.global_project, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) clusters = senlin_client.clusters(**queries) formatters = {} if parsed_args.global_project: columns.append('project_id') if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8] } if 'project_id' in columns: formatters['project_id'] = lambda x: x[:8] return ( columns, (utils.get_item_properties(c, columns, formatters=formatters) for c in clusters) ) class ShowCluster(command.ShowOne): """Show details of the cluster.""" log = logging.getLogger(__name__ + ".ShowCluster") def get_parser(self, prog_name): parser = super(ShowCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to show') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering return _show_cluster(senlin_client, parsed_args.cluster) def _show_cluster(senlin_client, cluster_id): try: cluster = senlin_client.get_cluster(cluster_id) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Cluster not found: %s') % cluster_id) formatters = { 'config': senlin_utils.json_formatter, 'metadata': senlin_utils.json_formatter, 'node_ids': senlin_utils.list_formatter } data = cluster.to_dict() if 'is_profile_only' in data: data.pop('is_profile_only') columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) class CreateCluster(command.ShowOne): """Create the cluster.""" log = logging.getLogger(__name__ + ".CreateCluster") def get_parser(self, prog_name): parser = super(CreateCluster, self).get_parser(prog_name) parser.add_argument( '--config', metavar='<"key1=value1;key2=value2...">', help=_('Configuration of the cluster. Default to {}. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon.'), action='append' ) parser.add_argument( '--min-size', metavar='', default=0, help=_('Min size of the cluster. Default to 0') ) parser.add_argument( '--max-size', metavar='', default=-1, help=_('Max size of the cluster. Default to -1, means unlimited') ) parser.add_argument( '--desired-capacity', metavar='', default=0, help=_('Desired capacity of the cluster. Default to min_size if ' 'min_size is specified else 0.') ) parser.add_argument( '--timeout', metavar='', type=int, help=_('Cluster creation timeout in seconds') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_('Metadata values to be attached to the cluster. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon.'), action='append' ) parser.add_argument( '--profile', metavar='', required=True, help=_('Default profile Id or name used for this cluster') ) parser.add_argument( 'name', metavar='', help=_('Name of the cluster to create') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering if parsed_args.min_size and not parsed_args.desired_capacity: parsed_args.desired_capacity = parsed_args.min_size attrs = { 'config': senlin_utils.format_parameters(parsed_args.config), 'name': parsed_args.name, 'profile_id': parsed_args.profile, 'min_size': parsed_args.min_size, 'max_size': parsed_args.max_size, 'desired_capacity': parsed_args.desired_capacity, 'metadata': senlin_utils.format_parameters(parsed_args.metadata), 'timeout': parsed_args.timeout } cluster = senlin_client.create_cluster(**attrs) return _show_cluster(senlin_client, cluster.id) class UpdateCluster(command.ShowOne): """Update the cluster.""" log = logging.getLogger(__name__ + ".UpdateCluster") def get_parser(self, prog_name): parser = super(UpdateCluster, self).get_parser(prog_name) parser.add_argument( '--config', metavar='<"key1=value1;key2=value2...">', help=_('s of the cluster. Default to {}. ' 'This can be specified multiple times, or once with ' 'key-value pairs separated by a semicolon.'), action='append' ) parser.add_argument( '--profile', metavar='', help=_('ID or name of new profile to use') ) parser.add_argument( '--profile-only', default=False, metavar='', help=_("Whether the cluster should be updated profile only. " "If false, it will be applied to all existing nodes. " "If true, any newly created nodes will use the new profile," "but existing nodes will not be changed. Default is False.") ) parser.add_argument( '--timeout', metavar='', help=_('New timeout (in seconds) value for the cluster') ) parser.add_argument( '--metadata', metavar='<"key1=value1;key2=value2...">', help=_("Metadata values to be attached to the cluster. " "This can be specified multiple times, or once with " "key-value pairs separated by a semicolon. Use '{}' " "can clean metadata "), action='append' ) parser.add_argument( '--name', metavar='', help=_('New name for the cluster to update') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to be updated') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering cluster = senlin_client.find_cluster(parsed_args.cluster) if cluster is None: raise exc.CommandError(_('Cluster not found: %s') % parsed_args.cluster) attrs = { 'name': parsed_args.name, 'profile_id': parsed_args.profile, 'profile_only': strutils.bool_from_string( parsed_args.profile_only, strict=True, ), 'metadata': senlin_utils.format_parameters(parsed_args.metadata), 'timeout': parsed_args.timeout, } senlin_client.update_cluster(cluster, **attrs) return _show_cluster(senlin_client, cluster.id) class DeleteCluster(command.Command): """Delete the cluster(s).""" log = logging.getLogger(__name__ + ".DeleteCluster") def get_parser(self, prog_name): parser = super(DeleteCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', nargs='+', help=_('Name or ID of cluster(s) to delete.') ) parser.add_argument( '--force-delete', action='store_true', help=_('Force to delete cluster(s).') ) parser.add_argument( '--force', action='store_true', help=_('Skip yes/no prompt (assume yes).') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering try: if not parsed_args.force and sys.stdin.isatty(): sys.stdout.write( _("Are you sure you want to delete this cluster(s)" " [y/N]?")) sys.stdout.flush() prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info('Ctrl-c detected.') return except EOFError: # Ctrl-d self.log.info('Ctrl-d detected') return result = {} for cid in parsed_args.cluster: try: cluster_delete_action = senlin_client.delete_cluster( cid, False, parsed_args.force_delete) result[cid] = ('OK', cluster_delete_action['id']) except Exception as ex: result[cid] = ('ERROR', six.text_type(ex)) for rid, res in result.items(): senlin_utils.print_action_result(rid, res) class ResizeCluster(command.Command): """Resize a cluster.""" log = logging.getLogger(__name__ + ".ResizeCluster") def get_parser(self, prog_name): parser = super(ResizeCluster, self).get_parser(prog_name) parser.add_argument( '--capacity', metavar='', type=int, help=_('The desired number of nodes of the cluster') ) parser.add_argument( '--adjustment', metavar='', type=int, help=_('A positive integer meaning the number of nodes to add, ' 'or a negative integer indicating the number of nodes to ' 'remove') ) parser.add_argument( '--percentage', metavar='', type=float, help=_('A value that is interpreted as the percentage of size ' 'adjustment. This value can be positive or negative') ) parser.add_argument( '--min-step', metavar='', type=int, help=_('An integer specifying the number of nodes for adjustment ' 'when is specified') ) parser.add_argument( '--strict', action='store_true', default=False, help=_('A boolean specifying whether the resize should be ' 'performed on a best-effort basis when the new capacity ' 'may go beyond size constraints') ) parser.add_argument( '--min-size', metavar='min', type=int, help=_('New lower bound of cluster size') ) parser.add_argument( '--max-size', metavar='max', type=int, help=_('New upper bound of cluster size. A value of -1 indicates ' 'no upper limit on cluster size') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering action_args = {} capacity = parsed_args.capacity adjustment = parsed_args.adjustment percentage = parsed_args.percentage min_size = parsed_args.min_size max_size = parsed_args.max_size min_step = parsed_args.min_step if sum(v is not None for v in (capacity, adjustment, percentage, min_size, max_size)) == 0: raise exc.CommandError(_("At least one parameter of 'capacity', " "'adjustment', 'percentage', 'min_size' " "and 'max_size' should be specified.")) if sum(v is not None for v in (capacity, adjustment, percentage)) > 1: raise exc.CommandError(_("Only one of 'capacity', 'adjustment' and" " 'percentage' can be specified.")) action_args['adjustment_type'] = None action_args['number'] = None if capacity is not None: if capacity < 0: raise exc.CommandError(_('Cluster capacity must be larger than' ' or equal to zero.')) action_args['adjustment_type'] = 'EXACT_CAPACITY' action_args['number'] = capacity if adjustment is not None: if adjustment == 0: raise exc.CommandError(_('Adjustment cannot be zero.')) action_args['adjustment_type'] = 'CHANGE_IN_CAPACITY' action_args['number'] = adjustment if percentage is not None: if (percentage == 0 or percentage == 0.0): raise exc.CommandError(_('Percentage cannot be zero.')) action_args['adjustment_type'] = 'CHANGE_IN_PERCENTAGE' action_args['number'] = percentage if min_step is not None and percentage is None: raise exc.CommandError(_('Min step is only used with ' 'percentage.')) if min_size is not None: if min_size < 0: raise exc.CommandError(_('Min size cannot be less than zero.')) if max_size is not None and max_size >= 0 and min_size > max_size: raise exc.CommandError(_('Min size cannot be larger than ' 'max size.')) if capacity is not None and min_size > capacity: raise exc.CommandError(_('Min size cannot be larger than the ' 'specified capacity')) if max_size is not None: if capacity is not None and max_size > 0 and max_size < capacity: raise exc.CommandError(_('Max size cannot be less than the ' 'specified capacity.')) # do a normalization if max_size < 0: max_size = -1 action_args['min_size'] = min_size action_args['max_size'] = max_size action_args['min_step'] = min_step action_args['strict'] = parsed_args.strict resp = senlin_client.resize_cluster(parsed_args.cluster, **action_args) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ScaleInCluster(command.Command): """Scale in a cluster by the specified number of nodes.""" log = logging.getLogger(__name__ + ".ScaleInCluster") def get_parser(self, prog_name): parser = super(ScaleInCluster, self).get_parser(prog_name) parser.add_argument( '--count', metavar='', help=_('Number of nodes to be deleted from the specified cluster') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering resp = senlin_client.scale_in_cluster(parsed_args.cluster, parsed_args.count) status_code = resp.get('code', None) if status_code in [409]: raise exc.CommandError(_( 'Unable to scale in cluster: %s') % resp['error']['message']) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ScaleOutCluster(command.Command): """Scale out a cluster by the specified number of nodes.""" log = logging.getLogger(__name__ + ".ScaleOutCluster") def get_parser(self, prog_name): parser = super(ScaleOutCluster, self).get_parser(prog_name) parser.add_argument( '--count', metavar='', help=_('Number of nodes to be added to the specified cluster') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering resp = senlin_client.scale_out_cluster(parsed_args.cluster, parsed_args.count) status_code = resp.get('code', None) if status_code in [409]: raise exc.CommandError(_( 'Unable to scale out cluster: %s') % resp['error']['message']) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterPolicyAttach(command.Command): """Attach policy to cluster.""" log = logging.getLogger(__name__ + ".ClusterPolicyAttach") def get_parser(self, prog_name): parser = super(ClusterPolicyAttach, self).get_parser(prog_name) parser.add_argument( '--enabled', metavar='', default=True, help=_('Whether the policy should be enabled once attached. ' 'Default to True') ) parser.add_argument( '--policy', metavar='', required=True, help=_('ID or name of policy to be attached') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering kwargs = { 'enabled': strutils.bool_from_string(parsed_args.enabled, strict=True), } resp = senlin_client.attach_policy_to_cluster(parsed_args.cluster, parsed_args.policy, **kwargs) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterPolicyDetach(command.Command): """Detach policy from cluster.""" log = logging.getLogger(__name__ + ".ClusterPolicyDetach") def get_parser(self, prog_name): parser = super(ClusterPolicyDetach, self).get_parser(prog_name) parser.add_argument( '--policy', metavar='', required=True, help=_('ID or name of policy to be detached') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering resp = senlin_client.detach_policy_from_cluster(parsed_args.cluster, parsed_args.policy) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterNodeList(command.Lister): """List nodes from cluster.""" log = logging.getLogger(__name__ + ".ClusterNodeList") def get_parser(self, prog_name): parser = super(ClusterNodeList, self).get_parser(prog_name) parser.add_argument( '--filters', metavar='', help=_("Filter parameters to apply on returned nodes. " "This can be specified multiple times, or once with " "parameters separated by a semicolon. The valid filter " "keys are: ['status', 'name']"), action='append' ) parser.add_argument( '--sort', metavar='[:]', help=_("Sorting option which is a string containing a list of " "keys separated by commas. Each key can be optionally " "appended by a sort direction (:asc or :desc)' The valid " "sort keys are:['index', 'name', 'status', 'init_at', " "'created_at', 'updated_at']") ) parser.add_argument( '--limit', metavar='', help=_('Limit the number of nodes returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return nodes that appear after the given node ID') ) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to nodes from') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering queries = { 'cluster_id': parsed_args.cluster, 'sort': parsed_args.sort, 'limit': parsed_args.limit, 'marker': parsed_args.marker, } if parsed_args.filters: queries.update(senlin_utils.format_parameters(parsed_args.filters)) nodes = senlin_client.nodes(**queries) if not parsed_args.full_id: formatters = { 'id': lambda x: x[:8], 'physical_id': lambda x: x[:8] if x else '' } else: formatters = {} columns = ['id', 'name', 'index', 'status', 'physical_id', 'created_at'] return ( columns, (utils.get_item_properties(n, columns, formatters=formatters) for n in nodes) ) class ClusterNodeAdd(command.Command): """Add specified nodes to cluster.""" log = logging.getLogger(__name__ + ".ClusterNodeAdd") def get_parser(self, prog_name): parser = super(ClusterNodeAdd, self).get_parser(prog_name) parser.add_argument( '--nodes', metavar='', required=True, help=_('ID or name of nodes to be added; multiple nodes can be' ' separated with ","') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering node_ids = parsed_args.nodes.split(',') resp = senlin_client.add_nodes_to_cluster(parsed_args.cluster, node_ids) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterNodeDel(command.Command): """Delete specified nodes from cluster.""" log = logging.getLogger(__name__ + ".ClusterNodeDel") def get_parser(self, prog_name): parser = super(ClusterNodeDel, self).get_parser(prog_name) parser.add_argument( '--nodes', metavar='', required=True, help=_('Name or ID of nodes to be deleted; multiple nodes can be ' 'separated with ","') ) parser.add_argument( '-d', '--destroy-after-deletion', required=False, default=False, help=_('Whether nodes should be destroyed after deleted. ' 'Default is False.') ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering node_ids = parsed_args.nodes.split(',') destroy = parsed_args.destroy_after_deletion destroy = strutils.bool_from_string(destroy, strict=True) kwargs = {"destroy_after_deletion": destroy} resp = senlin_client.remove_nodes_from_cluster( parsed_args.cluster, node_ids, **kwargs) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterNodeReplace(command.Command): """Replace the nodes in a cluster with specified nodes.""" log = logging.getLogger(__name__ + ".ClusterNodeReplace") def get_parser(self, prog_name): parser = super(ClusterNodeReplace, self).get_parser(prog_name) parser.add_argument( '--nodes', metavar='', required=True, help=_("OLD_NODE is the name or ID of a node to be replaced, " "NEW_NODE is the name or ID of a node as replacement. " "This can be specified multiple times, or once with " "node-pairs separated by a comma ','."), action='append' ) parser.add_argument( 'cluster', metavar='', help=_('Name or ID of cluster to operate on') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering nodepairs = {} for nodepair in parsed_args.nodes: key = nodepair.split('=')[0] value = nodepair.split('=')[1] nodepairs[key] = value resp = senlin_client.replace_nodes_in_cluster(parsed_args.cluster, nodepairs) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class CheckCluster(command.Command): """Check the cluster(s).""" log = logging.getLogger(__name__ + ".CheckCluster") def get_parser(self, prog_name): parser = super(CheckCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', nargs='+', help=_('ID or name of cluster(s) to operate on.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering for cid in parsed_args.cluster: try: resp = senlin_client.check_cluster(cid) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Cluster not found: %s') % cid) if 'action' in resp: print('Cluster check request on cluster %(cid)s is ' 'accepted by action %(action)s.' % {'cid': cid, 'action': resp['action']}) else: print('Request error: %s' % resp) class RecoverCluster(command.Command): """Recover the cluster(s).""" log = logging.getLogger(__name__ + ".RecoverCluster") def get_parser(self, prog_name): parser = super(RecoverCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', nargs='+', help=_('ID or name of cluster(s) to operate on.') ) parser.add_argument( '--check', metavar='', default=False, help=_("Whether the cluster should check it's nodes status before " "doing cluster recover. Default is false") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering params = { 'check': strutils.bool_from_string(parsed_args.check, strict=True) } for cid in parsed_args.cluster: try: resp = senlin_client.recover_cluster(cid, **params) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Cluster not found: %s') % cid) if 'action' in resp: print('Cluster recover request on cluster %(cid)s is ' 'accepted by action %(action)s.' % {'cid': cid, 'action': resp['action']}) else: print('Request error: %s' % resp) class ClusterCollect(command.Lister): """Collect attributes across a cluster.""" log = logging.getLogger(__name__ + ".ClusterCollect") def get_parser(self, prog_name): parser = super(ClusterCollect, self).get_parser(prog_name) parser.add_argument( '--full-id', default=False, action="store_true", help=_('Print full IDs in list') ) parser.add_argument( '--path', metavar='', required=True, help=_('JSON path expression for attribute to be collected') ) parser.add_argument( 'cluster', metavar='', help=_('ID or name of cluster(s) to operate on.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering attrs = senlin_client.collect_cluster_attrs(parsed_args.cluster, parsed_args.path) columns = ['node_id', 'attr_value'] formatters = {} if not parsed_args.full_id: formatters = { 'node_id': lambda x: x[:8] } return (columns, (utils.get_item_properties(a, columns, formatters=formatters) for a in attrs)) class ClusterOp(command.Lister): """Perform an operation on all nodes across a cluster.""" log = logging.getLogger(__name__ + ".ClusterOp") def get_parser(self, prog_name): parser = super(ClusterOp, self).get_parser(prog_name) parser.add_argument( '--operation', metavar='', required=True, help=_('Operation to be performed on the cluster') ) parser.add_argument( '--params', metavar='', help=_("Parameters to for the specified operation. " "This can be specified multiple times, or once with " "parameters separated by a semicolon."), action='append' ) parser.add_argument( 'cluster', metavar='', help=_('ID or name of cluster to operate on.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering cid = parsed_args.cluster if parsed_args.params: params = senlin_utils.format_parameters(parsed_args.params) else: params = {} try: resp = senlin_client.perform_operation_on_cluster( cid, parsed_args.operation, **params) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Cluster not found: %s') % cid) if 'action' in resp: print('Request accepted by action: %s' % resp['action']) else: print('Request error: %s' % resp) class ClusterRun(command.Command): """Run scripts on cluster.""" log = logging.getLogger(__name__ + ".ClusterRun") def get_parser(self, prog_name): parser = super(ClusterRun, self).get_parser(prog_name) parser.add_argument( '--port', metavar='', type=int, default=22, help=_('The TCP port to use for SSH connection') ) parser.add_argument( '--address-type', metavar='', default='floating', help=_("The type of IP address to use. Possible values include " "'fixed' and 'floating' (the default)") ) parser.add_argument( '--network', metavar='', default='', help=_("The network to use for SSH connection") ) parser.add_argument( '--ipv6', action="store_true", default=False, help=_("Whether the IPv6 address should be used for SSH. Default " "to use IPv4 address.") ) parser.add_argument( '--user', metavar='', default='root', help=_("The login name to use for SSH connection. Default to " "'root'.") ) parser.add_argument( '--identity-file', metavar='', help=_("The private key file to use, same as the '-i' SSH option") ) parser.add_argument( '--ssh-options', metavar='', default="", help=_("Extra options to pass to SSH. See: man ssh.") ) parser.add_argument( '--script', metavar='