# 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 six 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.i18n import _LI from senlinclient.common import utils as senlin_utils class ListCluster(command.Lister): """List the user's 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;key=value...>', 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='<key>[:<direction>]', 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='<limit>', help=_('Limit the number of clusters returned') ) parser.add_argument( '--marker', metavar='<id>', 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 not parsed_args.full_id: formatters = { '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='<cluster>', 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 = { 'metadata': senlin_utils.json_formatter, 'nodes': senlin_utils.list_formatter } data = cluster.to_dict() columns = sorted(list(six.iterkeys(data))) 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( '--profile', metavar='<profile>', required=True, help=_('Profile Id used for this cluster') ) parser.add_argument( '--min-size', metavar='<min-size>', default=0, help=_('Min size of the cluster. Default to 0') ) parser.add_argument( '--max-size', metavar='<max-size>', default=-1, help=_('Max size of the cluster. Default to -1, means unlimited') ) parser.add_argument( '--desired-capacity', metavar='<desired-capacity>', default=0, help=_('Desired capacity of the cluster. Default to min_size if ' 'min_size is specified else 0.') ) parser.add_argument( '--timeout', metavar='<timeout>', 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( 'name', metavar='<cluster-name>', 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 = { '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( '--profile', metavar='<profile>', help=_('ID or name of new profile to use') ) parser.add_argument( '--timeout', metavar='<timeout>', 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'), action='append' ) parser.add_argument( '--name', metavar='<name>', help=_('New name for the cluster to update') ) parser.add_argument( 'cluster', metavar='<cluster>', 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, 'metadata': senlin_utils.format_parameters(parsed_args.metadata), 'timeout': parsed_args.timeout, } senlin_client.update_cluster(cluster.id, **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='<cluster>', nargs='+', help=_('Name or ID of cluster(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 cluster(s)" " [y/N]?")) prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): return except KeyboardInterrupt: # Ctrl-c self.log.info(_LI('Ctrl-c detected.')) return except EOFError: # Ctrl-d self.log.info(_LI('Ctrl-d detected')) return failure_count = 0 for cid in parsed_args.cluster: try: senlin_client.delete_cluster(cid, 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 cluster(s).') % {'count': failure_count, 'total': len(parsed_args.cluster)}) print('Request accepted') 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='<capacity>', type=int, help=_('The desired number of nodes of the cluster') ) parser.add_argument( '--adjustment', metavar='<adjustment>', 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='<percentage>', 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='<min-step>', type=int, help=_('An integer specifying the number of nodes for adjustment ' 'when <PERCENTAGE> 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='<cluster>', 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)) > 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: if 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.cluster_resize(parsed_args.cluster, **action_args) print('Request accepted by action: %s' % resp['action']) 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='<count>', help=_('Number of nodes to be deleted from the specified cluster') ) parser.add_argument( 'cluster', metavar='<cluster>', 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.cluster_scale_in(parsed_args.cluster, parsed_args.count) print('Request accepted by action %s' % resp['action']) 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='<count>', help=_('Number of nodes to be added to the specified cluster') ) parser.add_argument( 'cluster', metavar='<cluster>', 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.cluster_scale_out(parsed_args.cluster, parsed_args.count) print('Request accepted by action %s' % resp['action']) 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( '--policy', metavar='<policy>', required=True, help=_('ID or name of policy to be attached') ) parser.add_argument( '--enabled', default=True, action="store_true", help=_('Whether the policy should be enabled once attached. ' 'Default to True') ) parser.add_argument( 'cluster', metavar='<cluster>', 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': parsed_args.enabled, } resp = senlin_client.cluster_attach_policy(parsed_args.cluster, parsed_args.policy, **kwargs) print('Request accepted by action: %s' % resp['action']) 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='<policy>', required=True, help=_('ID or name of policy to be detached') ) parser.add_argument( 'cluster', metavar='<cluster>', 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.cluster_detach_policy(parsed_args.cluster, parsed_args.policy) print('Request accepted by action: %s' % resp['action']) 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='<key1=value;key2=value2...>', help=_('Filter parameters to apply on returned nodes. ' 'This can be specified multiple times, or once with ' 'parameters separated by a semicolon'), action='append' ) parser.add_argument( '--sort', metavar='<key>[:<direction>]', 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)') ) parser.add_argument( '--limit', metavar='<limit>', help=_('Limit the number of nodes returned') ) parser.add_argument( '--marker', metavar='<id>', 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='<cluster>', 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='<nodes>', required=True, help=_('ID or name of nodes to be added; multiple nodes can be' ' separated with ","') ) parser.add_argument( 'cluster', metavar='<cluster>', 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.cluster_add_nodes(parsed_args.cluster, node_ids) print('Request accepted by action: %s' % resp['action']) 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='<nodes>', required=True, help=_('Name or ID of nodes to be deleted; multiple nodes can be ' 'separated with ","') ) parser.add_argument( 'cluster', metavar='<cluster>', 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.cluster_del_nodes(parsed_args.cluster, node_ids) print('Request accepted by action: %s' % resp['action']) 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='<cluster>', 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) print('Cluster check request on cluster %(cid)s is accepted by ' 'action %(action)s.' % {'cid': cid, 'action': resp['action']}) 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='<cluster>', 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.recover_cluster(cid) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Cluster not found: %s') % cid) print('Cluster recover request on cluster %(cid)s is accepted by ' 'action %(action)s.' % {'cid': cid, 'action': resp['action']})