示例#1
0
 def test_compare(self):
     self.assertTrue(version._compare('1.0', version.gt('0.9')))
     self.assertTrue(version._compare('1.0', version.ge('0.9')))
     self.assertTrue(version._compare('1.0', version.ge('1.0')))
     self.assertTrue(version._compare('1.0', version.eq('1.0')))
     self.assertTrue(version._compare('1.0', version.le('1.0')))
     self.assertTrue(version._compare('1.0', version.le('1.1')))
     self.assertTrue(version._compare('1.0', version.lt('1.1')))
     self.assertTrue(
         version._compare('1.1', version.gt('1.0'), version.lt('1.2')))
     self.assertTrue(
         version._compare('0.3',
                          version.eq('0.2'),
                          version.eq('0.3'),
                          op=any))
     self.assertFalse(version._compare('1.0', version.gt('1.0')))
     self.assertFalse(version._compare('1.0', version.ge('1.1')))
     self.assertFalse(version._compare('1.0', version.eq('1.1')))
     self.assertFalse(version._compare('1.0', version.le('0.9')))
     self.assertFalse(version._compare('1.0', version.lt('0.9')))
     self.assertRaises(ValueError, version._compare, 'abc',
                       version.le('1.1'))
     self.assertRaises(ValueError, version._compare, '1.0',
                       version.le('.0'))
     self.assertRaises(ValueError, version._compare, '1', version.le('2'))
示例#2
0
    def get_parser(self, prog_name):
        parser = super(SetAllocation, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the consumer')
        parser.add_argument(
            '--allocation',
            metavar='<rp=resource-provider-id,'
            'resource-class-name=amount-of-resource-used>',
            action='append',
            default=[],
            help='Create (or update) an allocation of a resource class. '
            'Specify option multiple times to set multiple allocations.')
        parser.add_argument('--project-id',
                            metavar='project_id',
                            help='ID of the consuming project. '
                            'This option is required starting from '
                            '``--os-placement-api-version 1.8``.',
                            required=self.compare_version(version.ge('1.8')))
        parser.add_argument('--user-id',
                            metavar='user_id',
                            help='ID of the consuming user. '
                            'This option is required starting from '
                            '``--os-placement-api-version 1.8``.',
                            required=self.compare_version(version.ge('1.8')))
        parser.add_argument('--consumer-type',
                            metavar='consumer_type',
                            help='The type of the consumer. '
                            'This option is required starting from '
                            '``--os-placement-api-version 1.38``.',
                            required=self.compare_version(version.ge('1.38')))
        return parser
示例#3
0
    def test_compare(self):
        self.assertTrue(version._compare('1.0', version.gt('0.9')))
        self.assertTrue(version._compare('1.0', version.ge('0.9')))
        self.assertTrue(version._compare('1.0', version.ge('1.0')))
        self.assertTrue(version._compare('1.0', version.eq('1.0')))
        self.assertTrue(version._compare('1.0', version.le('1.0')))
        self.assertTrue(version._compare('1.0', version.le('1.1')))
        self.assertTrue(version._compare('1.0', version.lt('1.1')))
        self.assertTrue(
            version._compare('1.1', version.gt('1.0'), version.lt('1.2')))
        self.assertTrue(
            version._compare('0.3',
                             version.eq('0.2'),
                             version.eq('0.3'),
                             op=any))

        # Test error message
        msg = 'Operation or argument is not supported with version 1.0; '
        self.assertEqual((msg + 'requires version greater than 1.0'),
                         version._compare('1.0', version.gt('1.0')))
        self.assertEqual((msg + 'requires at least version 1.1'),
                         version._compare('1.0', version.ge('1.1')))
        self.assertEqual((msg + 'requires version 1.1'),
                         version._compare('1.0', version.eq('1.1')))
        self.assertEqual((msg + 'requires at most version 0.9'),
                         version._compare('1.0', version.le('0.9')))
        self.assertEqual((msg + 'requires version less than 0.9'),
                         version._compare('1.0', version.lt('0.9')))

        self.assertRaises(ValueError, version._compare, 'abc',
                          version.le('1.1'))
        self.assertRaises(ValueError, version._compare, '1.0',
                          version.le('.0'))
        self.assertRaises(ValueError, version._compare, '1', version.le('2'))

        ex = self.assertRaises(ValueError, version.compare, '1.0',
                               version.ge('1.1'))
        self.assertEqual(
            'Operation or argument is not supported with version 1.0; '
            'requires at least version 1.1', str(ex))
        ex = self.assertRaises(ValueError,
                               version.compare,
                               '1.0',
                               version.eq('1.1'),
                               version.eq('1.5'),
                               op=any)
        self.assertEqual(
            'Operation or argument is not supported with version 1.0; '
            'requires version 1.1, or requires version 1.5', str(ex))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        filters = {}
        if parsed_args.name:
            filters['name'] = parsed_args.name
        if parsed_args.uuid:
            filters['uuid'] = parsed_args.uuid
        if parsed_args.aggregate_uuid:
            self.check_version(version.ge('1.3'))
            self.deprecated_option_warning("--aggregate-uuid", "--member-of")
            filters['member_of'] = 'in:' + ','.join(parsed_args.aggregate_uuid)
        if parsed_args.resource:
            self.check_version(version.ge('1.4'))
            filters['resources'] = ','.join(
                resource.replace('=', ':')
                for resource in parsed_args.resource)
        if 'in_tree' in parsed_args and parsed_args.in_tree:
            self.check_version(version.ge('1.14'))
            filters['in_tree'] = parsed_args.in_tree

        # We need to handle required and forbidden together as they all end up
        # in the same query param on the API.
        # First just check that the requested feature is aligned with the
        # request microversion
        required_traits = []
        if 'required' in parsed_args and parsed_args.required:
            self.check_version(version.ge('1.18'))
            if any(',' in required for required in parsed_args.required):
                self.check_version(version.ge('1.39'))
            required_traits = parsed_args.required

        forbidden_traits = []
        if 'forbidden' in parsed_args and parsed_args.forbidden:
            self.check_version(version.ge('1.22'))
            forbidden_traits = ['!' + f for f in parsed_args.forbidden]

        # Then collect the required query params containing both required and
        # forbidden traits
        filters['required'] = common.get_required_query_param_from_args(
            required_traits, forbidden_traits)

        if 'member_of' in parsed_args and parsed_args.member_of:
            # Fail if --member-of but not high enough microversion.
            self.check_version(version.ge('1.3'))
            filters['member_of'] = [
                'in:' + aggs for aggs in parsed_args.member_of
            ]

        resources = http.request('GET', BASE_URL,
                                 params=filters).json()['resource_providers']

        fields = ('uuid', 'name', 'generation')
        if self.compare_version(version.ge('1.14')):
            fields += ('root_provider_uuid', 'parent_provider_uuid')

        rows = (utils.get_dict_properties(r, fields) for r in resources)
        return fields, rows
示例#5
0
class SetResourceProviderTrait(command.Lister):
    """Associate traits with the resource provider identified by {uuid}.

    All the associated traits will be replaced by the traits specified.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(SetResourceProviderTrait, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the resource provider.')

        parser.add_argument('--trait',
                            metavar='<trait>',
                            help='Name of the trait. May be repeated.',
                            default=[],
                            action='append')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = RP_BASE_URL.format(uuid=parsed_args.uuid)
        rp = http.request('GET', url).json()
        url = RP_TRAITS_URL.format(uuid=parsed_args.uuid)
        payload = {
            'resource_provider_generation': rp['generation'],
            'traits': parsed_args.trait
        }
        traits = http.request('PUT', url, json=payload).json()['traits']
        return FIELDS, [[t] for t in traits]
示例#6
0
class SetAggregate(command.Lister):
    """Associate a list of aggregates with the resource provider.

    Each request cleans up previously associated resource provider
    aggregates entirely and sets the new ones. Passing empty aggregate
    UUID list will remove all associations with aggregates for the
    particular resource provider.

    This command requires at least --os-placement-api-version 1.1.
    """
    def get_parser(self, prog_name):
        parser = super(SetAggregate, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<name>',
                            help='UUID of the resource provider')

        parser.add_argument(
            '--aggregate',
            metavar='<aggregate_uuid>',
            help='UUID of the aggregate. Specify multiple times to associate '
            'a resource provider with multiple aggregates.',
            action='append',
            default=[])

        return parser

    @version.check(version.ge('1.1'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL.format(uuid=parsed_args.uuid)
        resp = http.request('PUT', url, json=parsed_args.aggregate).json()
        return FIELDS, [[r] for r in resp['aggregates']]
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        data = {'name': parsed_args.name}

        if 'uuid' in parsed_args and parsed_args.uuid:
            data['uuid'] = parsed_args.uuid
        if ('parent_provider' in parsed_args and parsed_args.parent_provider):
            self.check_version(version.ge('1.14'))
            data['parent_provider_uuid'] = parsed_args.parent_provider

        resp = http.request('POST', BASE_URL, json=data)
        resource = http.request('GET', resp.headers['Location']).json()

        fields = ('uuid', 'name', 'generation')
        if self.compare_version(version.ge('1.14')):
            fields += ('root_provider_uuid', 'parent_provider_uuid')

        return fields, utils.get_dict_properties(resource, fields)
示例#8
0
    def test_check_mixin(self):
        class Test(version.CheckerMixin):
            app = mock.Mock()
            app.client_manager.placement.api_version = '1.2'

        t = Test()
        self.assertTrue(t.compare_version(version.le('1.3')))
        self.assertTrue(t.check_version(version.ge('1.0')))
        self.assertRaisesRegex(ValueError,
                               'Operation or argument is not supported',
                               t.check_version, version.lt('1.2'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL + '/' + parsed_args.uuid
        data = dict(name=parsed_args.name)
        # Not knowing the previous state of a resource the client cannot catch
        # it, but if the user tries to re-parent a resource provider the server
        # returns an easy to understand error:
        #     Unable to save resource provider RP-ID:
        #     Object action update failed because:
        #     re-parenting a provider is not currently allowed.
        #     (HTTP 400)
        if ('parent_provider' in parsed_args and parsed_args.parent_provider):
            self.check_version(version.ge('1.14'))
            data['parent_provider_uuid'] = parsed_args.parent_provider
        resource = http.request('PUT', url, json=data).json()

        fields = ('uuid', 'name', 'generation')
        if self.compare_version(version.ge('1.14')):
            fields += ('root_provider_uuid', 'parent_provider_uuid')

        return fields, utils.get_dict_properties(resource, fields)
示例#10
0
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL + '/' + parsed_args.uuid
        resp = http.request('GET', url).json()
        per_provider = resp['allocations'].items()
        props = {}

        fields = ('resource_provider', 'generation', 'resources')
        if self.compare_version(version.ge('1.12')):
            fields += ('project_id', 'user_id')
            props['project_id'] = resp.get('project_id')
            props['user_id'] = resp.get('user_id')
        if self.compare_version(version.ge('1.38')):
            fields += ('consumer_type', )
            props['consumer_type'] = resp.get('consumer_type')

        allocs = [
            dict(resource_provider=k, **props, **v) for k, v in per_provider
        ]

        rows = (utils.get_dict_properties(a, fields) for a in allocs)
        return fields, rows
示例#11
0
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL + '/' + parsed_args.uuid
        resource = http.request('GET', url).json()

        fields = ('uuid', 'name', 'generation')
        if self.compare_version(version.ge('1.14')):
            fields += ('root_provider_uuid', 'parent_provider_uuid')

        if parsed_args.allocations:
            allocs_url = ALLOCATIONS_URL.format(uuid=parsed_args.uuid)
            allocs = http.request('GET', allocs_url).json()['allocations']
            resource['allocations'] = allocs
            fields += ('allocations',)

        return fields, utils.get_dict_properties(resource, fields)
示例#12
0
class ListResourceClass(command.Lister):
    """Return a list of all resource classes.

    This command requires at least ``--os-placement-api-version 1.2``.
    """
    def get_parser(self, prog_name):
        parser = super(ListResourceClass, self).get_parser(prog_name)

        return parser

    @version.check(version.ge('1.2'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        resource_classes = http.request('GET',
                                        BASE_URL).json()['resource_classes']
        rows = (utils.get_dict_properties(i, FIELDS) for i in resource_classes)
        return FIELDS, rows
示例#13
0
class CreateResourceClass(command.Command):
    """Create a new resource class.

    This command requires at least ``--os-placement-api-version 1.2``.
    """
    def get_parser(self, prog_name):
        parser = super(CreateResourceClass, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the resource class')
        return parser

    @version.check(version.ge('1.2'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        http.request('POST', BASE_URL, json={'name': parsed_args.name})
示例#14
0
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        filters = {}
        if parsed_args.name:
            filters['name'] = parsed_args.name
        if parsed_args.uuid:
            filters['uuid'] = parsed_args.uuid
        if parsed_args.aggregate_uuid:
            self.check_version(version.ge('1.3'))
            self.deprecated_option_warning("--aggregate-uuid", "--member-of")
            filters['member_of'] = 'in:' + ','.join(parsed_args.aggregate_uuid)
        if parsed_args.resource:
            self.check_version(version.ge('1.4'))
            filters['resources'] = ','.join(
                resource.replace('=', ':')
                for resource in parsed_args.resource)
        if 'in_tree' in parsed_args and parsed_args.in_tree:
            self.check_version(version.ge('1.14'))
            filters['in_tree'] = parsed_args.in_tree
        if 'required' in parsed_args and parsed_args.required:
            self.check_version(version.ge('1.18'))
            filters['required'] = ','.join(parsed_args.required)
        if 'forbidden' in parsed_args and parsed_args.forbidden:
            self.check_version(version.ge('1.22'))
            forbidden_traits = ','.join(
                ['!' + f for f in parsed_args.forbidden])
            if 'required' in filters:
                filters['required'] += ',' + forbidden_traits
            else:
                filters['required'] = forbidden_traits
        if 'member_of' in parsed_args and parsed_args.member_of:
            # Fail if --member-of but not high enough microversion.
            self.check_version(version.ge('1.3'))
            filters['member_of'] = [
                'in:' + aggs for aggs in parsed_args.member_of]

        resources = http.request(
            'GET', BASE_URL, params=filters).json()['resource_providers']

        fields = ('uuid', 'name', 'generation')
        if self.compare_version(version.ge('1.14')):
            fields += ('root_provider_uuid', 'parent_provider_uuid')

        rows = (utils.get_dict_properties(r, fields) for r in resources)
        return fields, rows
示例#15
0
class ListResourceProviderTrait(command.Lister):
    """List traits associated with the resource provider identified by {uuid}.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(ListResourceProviderTrait, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the resource provider.')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = RP_TRAITS_URL.format(uuid=parsed_args.uuid)
        traits = http.request('GET', url).json()['traits']
        return FIELDS, [[t] for t in traits]
示例#16
0
class ListAggregate(command.Lister):
    """List resource provider aggregates.

    This command requires at least --os-placement-api-version 1.1.
    """
    def get_parser(self, prog_name):
        parser = super(ListAggregate, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the resource provider')

        return parser

    @version.check(version.ge('1.1'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL.format(uuid=parsed_args.uuid)
        resp = http.request('GET', url).json()
        return FIELDS, [[r] for r in resp['aggregates']]
示例#17
0
class DeleteTrait(command.Command):
    """Delete the trait specified by {name}.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(DeleteTrait, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the trait.')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = '/'.join([BASE_URL, parsed_args.name])

        http.request('DELETE', url)
示例#18
0
class ListTrait(command.Lister):
    """Return a list of valid trait strings.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(ListTrait, self).get_parser(prog_name)

        parser.add_argument(
            '--name',
            metavar='<name>',
            help=('A string to filter traits. The following options '
                  'are available: startswith operator filters the '
                  'traits whose name begins with a specific prefix, '
                  'e.g. name=startswith:CUSTOM, in operator filters '
                  'the traits whose name is in the specified list, '
                  'e.g. name=in:HW_CPU_X86_AVX,HW_CPU_X86_SSE, '
                  'HW_CPU_X86_INVALID_FEATURE.'))

        parser.add_argument(
            '--associated',
            action='store_true',
            help=('If this parameter is presented, the returned '
                  'traits will be those that are associated with at '
                  'least one resource provider.'))

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL
        params = {}
        if parsed_args.name:
            params['name'] = parsed_args.name
        if parsed_args.associated:
            params['associated'] = parsed_args.associated
        traits = http.request('GET', url, params=params).json()['traits']
        return FIELDS, [[t] for t in traits]
示例#19
0
class ShowTrait(command.ShowOne):
    """Check if a trait name exists in this cloud.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(ShowTrait, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the trait.')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = '/'.join([BASE_URL, parsed_args.name])

        http.request('GET', url)
        return FIELDS, [parsed_args.name]
示例#20
0
class ShowResourceClass(command.ShowOne):
    """Return a representation of the resource class identified by ``<name>``.

    This command requires at least ``--os-placement-api-version 1.2``.
    """
    def get_parser(self, prog_name):
        parser = super(ShowResourceClass, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the resource class')

        return parser

    @version.check(version.ge('1.2'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = PER_CLASS_URL.format(name=parsed_args.name)

        resource = http.request('GET', url).json()
        return FIELDS, utils.get_dict_properties(resource, FIELDS)
示例#21
0
class ResourceShowUsage(command.Lister, version.CheckerMixin):
    """Show resource usages for a project (and optionally user) per class.

    Gives a report of usage information for resources associated with the
    project identified by the ``project_id`` argument and user identified by
    the ``--user-id`` option.

    This command requires at least ``--os-placement-api-version 1.9``.

    """
    def get_parser(self, prog_name):
        parser = super(ResourceShowUsage, self).get_parser(prog_name)

        parser.add_argument('project_id',
                            metavar='<project-uuid>',
                            help='ID of the project.')
        parser.add_argument('--user-id',
                            metavar='<user-uuid>',
                            help='ID of the user.')

        return parser

    @version.check(version.ge('1.9'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = USAGES_URL
        params = {'project_id': parsed_args.project_id}
        if parsed_args.user_id:
            params['user_id'] = parsed_args.user_id
        per_class = http.request('GET', url, params=params).json()['usages']

        usages = [{
            'resource_class': k,
            'usage': v
        } for k, v in per_class.items()]
        rows = (utils.get_dict_properties(u, FIELDS) for u in usages)
        return FIELDS, rows
示例#22
0
class DeleteResourceClass(command.Command):
    """Delete the resource class identified by ``<name>``.

    Only custom resource classes can be deleted.

    This command requires at least ``--os-placement-api-version 1.2``.
    """
    def get_parser(self, prog_name):
        parser = super(DeleteResourceClass, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the resource class')

        return parser

    @version.check(version.ge('1.2'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = PER_CLASS_URL.format(name=parsed_args.name)

        http.request('DELETE', url)
示例#23
0
class DeleteResourceProviderTrait(command.Command):
    """Dissociate all the traits from the resource provider.

    Note that this command is not atomic if multiple processes are managing
    traits for the same provider.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(DeleteResourceProviderTrait, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the resource provider.')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = RP_TRAITS_URL.format(uuid=parsed_args.uuid)
        http.request('DELETE', url)
示例#24
0
class SetResourceClass(command.Command):
    """Create or validate the existence of single resource class.

    Unlike ``openstack resource class create``, this command also succeeds if
    the resource class already exists, which makes this an idempotent check or
    create command.

    This command requires at least ``--os-placement-api-version 1.7``.
    """
    def get_parser(self, prog_name):
        parser = super(SetResourceClass, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the resource class')
        return parser

    @version.check(version.ge('1.7'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL + '/' + parsed_args.name

        http.request('PUT', url)
示例#25
0
class CreateTrait(command.Command):
    """Create a new custom trait.

    Custom traits must begin with the prefix ``CUSTOM_`` and contain only the
    letters A through Z, the numbers 0 through 9 and the underscore "_"
    character.

    This command requires at least ``--os-placement-api-version 1.6``.
    """
    def get_parser(self, prog_name):
        parser = super(CreateTrait, self).get_parser(prog_name)

        parser.add_argument('name',
                            metavar='<name>',
                            help='Name of the trait.')

        return parser

    @version.check(version.ge('1.6'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = '/'.join([BASE_URL, parsed_args.name])
        http.request('PUT', url)
示例#26
0
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL.format(uuid=parsed_args.uuid)
        aggregate = parsed_args.aggregate
        generation = None
        if 'generation' in parsed_args and parsed_args.generation is not None:
            self.check_version(version.ge('1.19'))
            generation = parsed_args.generation

        if self.compare_version(version.lt('1.19')):
            resp = http.request('PUT', url, json=aggregate).json()
        # Microversion 1.19 and beyond a generation argument is
        # required to write aggregates.
        elif generation is not None:
            data = {
                'aggregates': aggregate,
                'resource_provider_generation': generation
            }
            resp = http.request('PUT', url, json=data).json()
        else:
            raise exceptions.CommandError('A generation must be specified.')

        return FIELDS, [[r] for r in resp['aggregates']]
示例#27
0
class ListAllocationCandidate(command.Lister, version.CheckerMixin):
    """List allocation candidates.

    Returns a representation of a collection of allocation requests and
    resource provider summaries. Each allocation request has information
    to issue an ``openstack resource provider allocation set`` request to claim
    resources against a related set of resource providers.

    As several allocation requests are available its necessary to select one.
    To make a decision, resource provider summaries are provided with the
    inventory/capacity information.

    For example::

      $ export OS_PLACEMENT_API_VERSION=1.10
      $ openstack allocation candidate list --resource VCPU=1
      +---+------------+-------------------------+-------------------------+
      | # | allocation | resource provider       | inventory used/capacity |
      +---+------------+-------------------------+-------------------------+
      | 1 | VCPU=1     | 66bcaca9-9263-45b1-a569 | VCPU=0/128              |
      |   |            | -ea708ff7a968           |                         |
      +---+------------+-------------------------+-------------------------+

    In this case, the user is looking for resource providers that can have
    capacity to allocate 1 ``VCPU`` resource class. There is one resource
    provider that can serve that allocation request and that resource providers
    current ``VCPU`` inventory used is 0 and available capacity is 128.

    This command requires at least ``--os-placement-api-version 1.10``.
    """
    def get_parser(self, prog_name):
        parser = super(ListAllocationCandidate, self).get_parser(prog_name)

        parser.add_argument(
            '--resource',
            metavar='<resource_class>=<value>',
            dest='resources',
            action=AppendToGroup,
            help='String indicating an amount of resource of a specified '
            'class that providers in each allocation request must '
            'collectively have the capacity and availability to serve. '
            'Can be specified multiple times per resource class. '
            'For example: '
            '``--resource VCPU=4 --resource DISK_GB=64 '
            '--resource MEMORY_MB=2048``')
        parser.add_argument('--limit',
                            metavar='<limit>',
                            help='A positive integer to limit '
                            'the maximum number of allocation candidates. '
                            'This option requires at least '
                            '``--os-placement-api-version 1.16``.')
        parser.add_argument(
            '--required',
            metavar='<required>',
            action=AppendToGroup,
            help='A required trait. May be repeated. Allocation candidates '
            'must collectively contain all of the required traits. '
            'This option requires at least '
            '``--os-placement-api-version 1.17``.')
        parser.add_argument(
            '--forbidden',
            metavar='<forbidden>',
            action=AppendToGroup,
            help='A forbidden trait. May be repeated. Returned allocation '
            'candidates must not contain any of the specified traits. '
            'This option requires at least '
            '``--os-placement-api-version 1.22``.')
        # NOTE(tetsuro): --aggregate-uuid is deprecated in Jan 2020 in 1.x
        # release. Do not remove before Jan 2021 and a 2.x release.
        aggregate_group = parser.add_mutually_exclusive_group()
        aggregate_group.add_argument(
            "--member-of",
            action=AppendToGroup,
            metavar='<member_of>',
            help='A list of comma-separated UUIDs of the resource provider '
            'aggregates. The returned allocation candidates must be '
            'associated with at least one of the aggregates identified '
            'by uuid. This param requires at least '
            '``--os-placement-api-version 1.21`` and can be repeated to '
            'add(restrict) the condition with '
            '``--os-placement-api-version 1.24`` or greater. '
            'For example, to get candidates in either of agg1 or agg2 '
            'and definitely in agg3, specify:\n\n'
            '``--member_of <agg1>,<agg2> --member_of <agg3>``')
        aggregate_group.add_argument('--aggregate-uuid',
                                     action=AppendToGroup,
                                     metavar='<aggregate_uuid>',
                                     help=argparse.SUPPRESS)
        parser.add_argument(
            '--group',
            action=GroupAction,
            metavar='<group>',
            help='An integer to group granular requests. If specified, '
            'following given options of resources, required/forbidden '
            'traits, and aggregate are associated to that group and will '
            'be satisfied by the same resource provider in the response. '
            'Can be repeated to get candidates from multiple resource '
            'providers in the same resource provider tree. '
            'For example, ``--group 1 --resource VCPU=3 --required '
            'HW_CPU_X86_AVX --group 2 --resource VCPU=2 --required '
            'HW_CPU_X86_SSE`` will provide candidates where three VCPUs '
            'comes from a provider with ``HW_CPU_X86_AVX`` trait and '
            'two VCPUs from a provider with ``HW_CPU_X86_SSE`` trait. '
            'This option requires at least '
            '``--os-placement-api-version 1.25`` or greater, but to have '
            'placement server be aware of resource provider tree, use '
            '``--os-placement-api-version 1.29`` or greater.')
        parser.add_argument(
            '--group-policy',
            choices=['none', 'isolate'],
            default='none',
            metavar='<group_policy>',
            help='This indicates how the groups should interact when multiple '
            'groups are supplied. With group_policy=none (default), '
            'separate groups may or may not be satisfied by the same '
            'provider. With group_policy=isolate, numbered groups are '
            'guaranteed to be satisfied by different providers.')

        return parser

    @version.check(version.ge('1.10'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        params = {}
        if 'groups' not in parsed_args:
            raise exceptions.CommandError(
                'At least one --resource must be specified.')

        if 'limit' in parsed_args and parsed_args.limit:
            # Fail if --limit but not high enough microversion.
            self.check_version(version.ge('1.16'))
            params['limit'] = int(parsed_args.limit)

        if any(parsed_args.groups):
            self.check_version(version.ge('1.25'))
            params['group_policy'] = parsed_args.group_policy

        for suffix, group in parsed_args.groups.items():

            def _get_key(name):
                return name + suffix

            if 'resources' not in group:
                raise exceptions.CommandError(
                    '--resources should be provided in group %s', suffix)
            for resource in group['resources']:
                if not len(resource.split('=')) == 2:
                    raise exceptions.CommandError(
                        'Arguments to --resource must be of form '
                        '<resource_class>=<value>')

            params[_get_key('resources')] = ','.join(
                resource.replace('=', ':') for resource in group['resources'])
            if 'required' in group and group['required']:
                # Fail if --required but not high enough microversion.
                self.check_version(version.ge('1.17'))
                params[_get_key('required')] = ','.join(group['required'])
            if 'forbidden' in group and group['forbidden']:
                self.check_version(version.ge('1.22'))
                forbidden_traits = ','.join(
                    ['!' + f for f in group['forbidden']])
                if 'required' in params:
                    params[_get_key('required')] += ',' + forbidden_traits
                else:
                    params[_get_key('required')] = forbidden_traits
            if 'aggregate_uuid' in group and group['aggregate_uuid']:
                # Fail if --aggregate_uuid but not high enough microversion.
                self.check_version(version.ge('1.21'))
                self.deprecated_option_warning("--aggregate-uuid",
                                               "--member-of")
                params[_get_key('member_of')] = 'in:' + ','.join(
                    group['aggregate_uuid'])
            if 'member_of' in group and group['member_of']:
                # Fail if --member-of but not high enough microversion.
                self.check_version(version.ge('1.21'))
                params[_get_key('member_of')] = [
                    'in:' + aggs for aggs in group['member_of']
                ]

        resp = http.request('GET', BASE_URL, params=params).json()

        rp_resources = {}
        include_traits = self.compare_version(version.ge('1.17'))
        if include_traits:
            rp_traits = {}
        for rp_uuid, resources in resp['provider_summaries'].items():
            rp_resources[rp_uuid] = ','.join(
                '%s=%s/%s' % (rc, value['used'], value['capacity'])
                for rc, value in resources['resources'].items())
            if include_traits:
                rp_traits[rp_uuid] = ','.join(resources['traits'])

        rows = []
        if self.compare_version(version.ge('1.12')):
            for i, allocation_req in enumerate(resp['allocation_requests']):
                for rp, resources in allocation_req['allocations'].items():
                    req = ','.join(
                        '%s=%s' % (rc, value)
                        for rc, value in resources['resources'].items())
                    if include_traits:
                        row = [i + 1, req, rp, rp_resources[rp], rp_traits[rp]]
                    else:
                        row = [i + 1, req, rp, rp_resources[rp]]
                    rows.append(row)
        else:
            for i, allocation_req in enumerate(resp['allocation_requests']):
                for allocation in allocation_req['allocations']:
                    rp = allocation['resource_provider']['uuid']
                    req = ','.join(
                        '%s=%s' % (rc, value)
                        for rc, value in allocation['resources'].items())
                    rows.append([i + 1, req, rp, rp_resources[rp]])

        fields = ('#', 'allocation', 'resource provider',
                  'inventory used/capacity')
        if include_traits:
            fields += ('traits', )

        return fields, rows
示例#28
0
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        params = {}
        if 'groups' not in parsed_args:
            raise exceptions.CommandError(
                'At least one --resource must be specified.')

        if 'limit' in parsed_args and parsed_args.limit:
            # Fail if --limit but not high enough microversion.
            self.check_version(version.ge('1.16'))
            params['limit'] = int(parsed_args.limit)

        if any(parsed_args.groups):
            self.check_version(version.ge('1.25'))
            params['group_policy'] = parsed_args.group_policy

        for suffix, group in parsed_args.groups.items():

            def _get_key(name):
                return name + suffix

            if 'resources' not in group:
                raise exceptions.CommandError(
                    '--resources should be provided in group %s', suffix)
            for resource in group['resources']:
                if not len(resource.split('=')) == 2:
                    raise exceptions.CommandError(
                        'Arguments to --resource must be of form '
                        '<resource_class>=<value>')

            params[_get_key('resources')] = ','.join(
                resource.replace('=', ':') for resource in group['resources'])
            if 'required' in group and group['required']:
                # Fail if --required but not high enough microversion.
                self.check_version(version.ge('1.17'))
                params[_get_key('required')] = ','.join(group['required'])
            if 'forbidden' in group and group['forbidden']:
                self.check_version(version.ge('1.22'))
                forbidden_traits = ','.join(
                    ['!' + f for f in group['forbidden']])
                if 'required' in params:
                    params[_get_key('required')] += ',' + forbidden_traits
                else:
                    params[_get_key('required')] = forbidden_traits
            if 'aggregate_uuid' in group and group['aggregate_uuid']:
                # Fail if --aggregate_uuid but not high enough microversion.
                self.check_version(version.ge('1.21'))
                self.deprecated_option_warning("--aggregate-uuid",
                                               "--member-of")
                params[_get_key('member_of')] = 'in:' + ','.join(
                    group['aggregate_uuid'])
            if 'member_of' in group and group['member_of']:
                # Fail if --member-of but not high enough microversion.
                self.check_version(version.ge('1.21'))
                params[_get_key('member_of')] = [
                    'in:' + aggs for aggs in group['member_of']
                ]

        resp = http.request('GET', BASE_URL, params=params).json()

        rp_resources = {}
        include_traits = self.compare_version(version.ge('1.17'))
        if include_traits:
            rp_traits = {}
        for rp_uuid, resources in resp['provider_summaries'].items():
            rp_resources[rp_uuid] = ','.join(
                '%s=%s/%s' % (rc, value['used'], value['capacity'])
                for rc, value in resources['resources'].items())
            if include_traits:
                rp_traits[rp_uuid] = ','.join(resources['traits'])

        rows = []
        if self.compare_version(version.ge('1.12')):
            for i, allocation_req in enumerate(resp['allocation_requests']):
                for rp, resources in allocation_req['allocations'].items():
                    req = ','.join(
                        '%s=%s' % (rc, value)
                        for rc, value in resources['resources'].items())
                    if include_traits:
                        row = [i + 1, req, rp, rp_resources[rp], rp_traits[rp]]
                    else:
                        row = [i + 1, req, rp, rp_resources[rp]]
                    rows.append(row)
        else:
            for i, allocation_req in enumerate(resp['allocation_requests']):
                for allocation in allocation_req['allocations']:
                    rp = allocation['resource_provider']['uuid']
                    req = ','.join(
                        '%s=%s' % (rc, value)
                        for rc, value in allocation['resources'].items())
                    rows.append([i + 1, req, rp, rp_resources[rp]])

        fields = ('#', 'allocation', 'resource provider',
                  'inventory used/capacity')
        if include_traits:
            fields += ('traits', )

        return fields, rows
示例#29
0
    def take_action(self, parsed_args):

        http = self.app.client_manager.placement

        if parsed_args.aggregate:
            self.check_version(version.ge('1.3'))
            filters = {'member_of': parsed_args.uuid}
            url = common.url_with_filters(RP_BASE_URL, filters)
            rps = http.request('GET', url).json()['resource_providers']
            if not rps:
                raise exceptions.CommandError(
                    'No resource providers found in aggregate with uuid %s.' %
                    parsed_args.uuid)
        else:
            url = RP_BASE_URL + '/' + parsed_args.uuid
            rps = [http.request('GET', url).json()]

        resources_list = []
        ret = 0
        for rp in rps:
            inventories = collections.defaultdict(dict)
            url = BASE_URL.format(uuid=rp['uuid'])
            if parsed_args.amend:
                # Get existing inventories
                # TODO(melwitt): Do something to handle the possibility of the
                # GET failing here (example: resource provider deleted from
                # underneath us).
                payload = http.request('GET', url).json()
                inventories.update(payload['inventories'])
                payload['inventories'] = inventories
            else:
                payload = {
                    'inventories': inventories,
                    'resource_provider_generation': rp['generation']
                }

            # Apply resource values to inventories
            for r in parsed_args.resource:
                name, field, value = parse_resource_argument(r)
                inventories[name][field] = value

            try:
                if not parsed_args.dry_run:
                    resources = http.request('PUT', url, json=payload).json()
                else:
                    resources = payload
            except Exception as exp:
                with excutils.save_and_reraise_exception() as err_ctx:
                    if parsed_args.aggregate:
                        self.log.error(
                            _('Failed to set inventory for '
                              'resource provider %(rp)s: %(exp)s.'), {
                                  'rp': rp['uuid'],
                                  'exp': exp
                              })
                        err_ctx.reraise = False
                        ret += 1
                        continue
            resources_list.append((rp['uuid'], resources))

        if ret > 0:
            msg = _('Failed to set inventory for %(ret)s of %(total)s '
                    'resource providers.') % {
                        'ret': ret,
                        'total': len(rps)
                    }
            raise exceptions.CommandError(msg)

        def get_rows(fields, resources, rp_uuid=None):
            inventories = [
                dict(resource_class=k, **v)
                for k, v in resources['inventories'].items()
            ]
            prepend = (rp_uuid, ) if rp_uuid else ()
            # This is a generator expression
            rows = (prepend + utils.get_dict_properties(i, fields)
                    for i in inventories)
            return rows

        fields = ('resource_class', ) + FIELDS
        if parsed_args.aggregate:
            # If this is an aggregate batch, create output that will include
            # resource provider as the first field to differentiate the values
            rows = ()
            for rp_uuid, resources in resources_list:
                subrows = get_rows(fields, resources, rp_uuid=rp_uuid)
                rows = itertools.chain(rows, subrows)
            fields = ('resource_provider', ) + fields
            return fields, rows
        else:
            # If this was not an aggregate batch, show output for the one
            # resource provider (show payload of the first item in the list),
            # keeping the behavior prior to the addition of --aggregate option
            return fields, get_rows(fields, resources_list[0][1])
示例#30
0
class SetAggregate(command.Lister, version.CheckerMixin):
    """Associate a list of aggregates with the resource provider.

    Each request cleans up previously associated resource provider
    aggregates entirely and sets the new ones. Passing empty aggregate
    UUID list will remove all associations with aggregates for the
    particular resource provider.

    This command requires at least ``--os-placement-api-version 1.1``.
    """
    def get_parser(self, prog_name):
        parser = super(SetAggregate, self).get_parser(prog_name)

        parser.add_argument('uuid',
                            metavar='<uuid>',
                            help='UUID of the resource provider')

        parser.add_argument(
            '--aggregate',
            metavar='<aggregate_uuid>',
            help='UUID of the aggregate. Specify multiple times to associate '
            'a resource provider with multiple aggregates.',
            action='append',
            default=[])

        parser.add_argument(
            '--generation',
            metavar='<resource_provider_generation>',
            type=int,
            help='The generation of resource provider. Must match the server-'
            'side generation of the resource provider or the operation '
            'will fail.\n\n'
            'This param requires at least '
            '``--os-placement-api-version 1.19``.')

        return parser

    @version.check(version.ge('1.1'))
    def take_action(self, parsed_args):
        http = self.app.client_manager.placement

        url = BASE_URL.format(uuid=parsed_args.uuid)
        aggregate = parsed_args.aggregate
        generation = None
        if 'generation' in parsed_args and parsed_args.generation is not None:
            self.check_version(version.ge('1.19'))
            generation = parsed_args.generation

        if self.compare_version(version.lt('1.19')):
            resp = http.request('PUT', url, json=aggregate).json()
        # Microversion 1.19 and beyond a generation argument is
        # required to write aggregates.
        elif generation is not None:
            data = {
                'aggregates': aggregate,
                'resource_provider_generation': generation
            }
            resp = http.request('PUT', url, json=data).json()
        else:
            raise exceptions.CommandError('A generation must be specified.')

        return FIELDS, [[r] for r in resp['aggregates']]