Пример #1
0
class server_wait(_CycladesInit, _ServerWait):
    """Wait for server to change its status (default: --while BUILD)"""

    arguments = dict(
        timeout=IntArgument(
            'Wait limit in seconds (default: 60)', '--timeout', default=60),
        status=StatusArgument(
            'DEPRECATED in next version, equivalent to "--while"',
            '--status',
            valid_states=server_states),
        status_w=StatusArgument(
            'Wait while in status (%s)' % ','.join(server_states), '--while',
            valid_states=server_states),
        status_u=StatusArgument(
            'Wait until status is reached (%s)' % ','.join(server_states),
            '--until',
            valid_states=server_states),
    )

    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.server_id
    def _run(self, server_id):
        r = self.client.get_server_details(server_id)

        if self['status_u']:
            if r['status'].lower() == self['status_u'].lower():
                self.error(
                    'Server %s: already in %s' % (server_id, r['status']))
            else:
                self.wait_until(
                    server_id, self['status_u'], timeout=self['timeout'])
        else:
            status_w = self['status_w'] or self['status'] or 'BUILD'
            if r['status'].lower() == status_w.lower():
                self.wait_while(
                    server_id, status_w, timeout=self['timeout'])
            else:
                self.error(
                    'Server %s status: %s' % (server_id, r['status']))

    def main(self, server_id):
        super(self.__class__, self)._run()

        status_args = [self['status'], self['status_w'], self['status_u']]
        if len([x for x in status_args if x]) > 1:
            raise CLIInvalidArgument(
                'Invalid argument combination', importance=2, details=[
                    'Arguments %s, %s and %s are mutually exclusive' % (
                        self.arguments['status'].lvalue,
                        self.arguments['status_w'].lvalue,
                        self.arguments['status_u'].lvalue)])
        if self['status']:
            self.error(
                'WARNING: argument %s will be deprecated '
                'in the next version, use %s instead' % (
                    self.arguments['status'].lvalue,
                    self.arguments['status_w'].lvalue))

        self._run(server_id=server_id)
Пример #2
0
class port_wait(_NetworkInit, _PortWait):
    """Wait for port to finish (default: BUILD)"""

    arguments = dict(port_status=StatusArgument(
        'Wait while in this status (%s, default: %s)' %
        (', '.join(port_states), port_states[0]),
        '--status',
        valid_states=port_states),
                     timeout=IntArgument('Wait limit in seconds (default: 60)',
                                         '--timeout',
                                         default=60))

    @errors.Generic.all
    @errors.Cyclades.connection
    def _run(self, port_id, port_status):
        port = self.client.get_port_details(port_id)
        if port['status'].lower() == port_status.lower():
            self.wait(port_id, port_status, timeout=self['timeout'])
        else:
            self.error('Port %s: Cannot wait for status %s, '
                       'status is already %s' %
                       (port_id, port_status, port['status']))

    def main(self, port_id):
        super(self.__class__, self)._run()
        port_status = self['port_status'] or port_states[0]
        self._run(port_id=port_id, port_status=port_status)
Пример #3
0
class server_wait(_CycladesInit, _ServerWait):
    """Wait for server to change its status (default: BUILD)"""

    arguments = dict(timeout=IntArgument('Wait limit in seconds (default: 60)',
                                         '--timeout',
                                         default=60),
                     server_status=StatusArgument(
                         'Status to wait for (%s, default: %s)' %
                         (', '.join(server_states), server_states[0]),
                         '--status',
                         valid_states=server_states))

    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.server_id
    def _run(self, server_id, current_status):
        r = self.client.get_server_details(server_id)
        if r['status'].lower() == current_status.lower():
            self.wait(server_id, current_status, timeout=self['timeout'])
        else:
            self.error('Server %s: Cannot wait for status %s, '
                       'status is already %s' %
                       (server_id, current_status, r['status']))

    def main(self, server_id):
        super(self.__class__, self)._run()
        self._run(server_id=server_id,
                  current_status=self['server_status'] or 'BUILD')
Пример #4
0
class flavor_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
    """List available hardware flavors"""

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
            '--more'),
        enum=FlagArgument('Enumerate results', '--enumerate'),
        ram=ValueArgument('filter by ram', ('--ram')),
        vcpus=ValueArgument('filter by number of VCPUs', ('--vcpus')),
        disk=ValueArgument('filter by disk size in GB', ('--disk')),
        disk_template=ValueArgument(
            'filter by disk_templace', ('--disk-template')),
        project_id=ValueArgument('filter by project ID', '--project'),
        is_public=FlagArgument('list only public flavors', '--public'),
    )

    def _apply_common_filters(self, flavors):
        common_filters = dict()
        if self['ram']:
            common_filters['ram'] = self['ram']
        if self['vcpus']:
            common_filters['vcpus'] = self['vcpus']
        if self['disk']:
            common_filters['disk'] = self['disk']
        if self['disk_template']:
            common_filters['SNF:disk_template'] = self['disk_template']
        return filter_dicts_by_dict(flavors, common_filters)

    @errors.Generic.all
    @errors.Cyclades.connection
    def _run(self):
        withcommons = self['ram'] or self['vcpus'] or (
            self['disk'] or self['disk_template'])
        detail = self['detail'] or withcommons
        flavors = self.client.list_flavors(
            detail, is_public=self['is_public'], project_id=self['project_id'])
        flavors = self._filter_by_name(flavors)
        flavors = self._filter_by_id(flavors)
        if withcommons:
            flavors = self._apply_common_filters(flavors)
        if not (self['detail'] or self['output_format']):
            remove_from_items(flavors, 'links')
        if detail and not self['detail']:
            for flv in flavors:
                for key in set(flv).difference(['id', 'name']):
                    flv.pop(key)
        kwargs = dict(out=StringIO(), title=()) if self['more'] else {}
        self.print_(flavors, with_enumeration=self['enum'], **kwargs)
        if self['more']:
            pager(kwargs['out'].getvalue())

    def main(self):
        super(self.__class__, self)._run()
        self._run()
Пример #5
0
class project_modify(_AstakosInit, OptionalOutput):
    """Modify properties of a project"""

    __doc__ += _project_specs
    arguments = dict(
        specs_path=ValueArgument('Specification file (contents in json)',
                                 '--spec-file'),
        project_name=ValueArgument('Name the project', '--name'),
        owner_uuid=ValueArgument('Project owner', '--owner'),
        homepage_url=ValueArgument('Project homepage', '--homepage'),
        description=ValueArgument('Describe the project', '--description'),
        max_members=IntArgument('Maximum subscribers', '--max-members'),
        private=FlagArgument('Make the project private', '--private'),
        public=FlagArgument('Make the project public', '--public'),
        start_date=DateArgument('When to start the project', '--start-date'),
        end_date=DateArgument('When to end the project', '--end-date'),
        join_policy=PolicyArgument(
            'Set join policy (%s)' % ', '.join(PolicyArgument.policies),
            '--join-policy'),
        leave_policy=PolicyArgument(
            'Set leave policy (%s)' % ', '.join(PolicyArgument.policies),
            '--leave-policy'),
        resource_capacities=ProjectResourceArgument(
            'Set the member and project capacities for resources (repeatable) '
            'e.g., --resource cyclades.cpu=1,5    means "members will have at '
            'most 1 cpu but the project will have at most 5"       To see all '
            'resources:   kamaki resource list', '--resource'))
    required = [
        'specs_path', 'owner_uuid', 'homepage_url', 'description', 'public',
        'private', 'project_name', 'start_date', 'end_date', 'join_policy',
        'leave_policy', 'resource_capacities', 'max_members'
    ]

    @errors.Generic.all
    @errors.Astakos.astakosclient
    @errors.Astakos.project_id
    @apply_notification
    def _run(self, project_id):
        specs = dict()
        if self['specs_path']:
            with open(abspath(self['specs_path'])) as f:
                specs = load(f)
        for key, arg in (('name', self['project_name']),
                         ('owner', self['owner_uuid']), ('homepage',
                                                         self['homepage_url']),
                         ('description',
                          self['description']), ('max_members',
                                                 self['max_members']),
                         ('start_date',
                          self.arguments['start_date'].isoformat),
                         ('end_date', self.arguments['end_date'].isoformat),
                         ('join_policy',
                          self['join_policy']), ('leave_policy',
                                                 self['leave_policy']),
                         ('resources', self['resource_capacities'])):
            if arg:
                specs[key] = arg
        private = self['private'] or (False if self['public'] else None)
        if private is not None:
            self['private'] = private

        self.print_(self.client.modify_project(project_id, specs),
                    self.print_dict)

    def main(self, project_id):
        super(self.__class__, self)._run()
        if self['private'] and self['public']:
            a = self.arguments
            raise CLIInvalidArgument(
                'Invalid argument combination',
                details=[
                    'Arguments %s and %s are mutually exclussive' %
                    (a['private'].lvalue, a['public'].lvalue)
                ])
        self._run(project_id=project_id)
Пример #6
0
class project_create(_AstakosInit, OptionalOutput):
    """Apply for a new project"""

    __doc__ += _project_specs
    arguments = dict(
        specs_path=ValueArgument('Specification file (contents in json)',
                                 '--spec-file'),
        project_name=ValueArgument('Name the project', '--name'),
        owner_uuid=ValueArgument('Project owner', '--owner'),
        homepage_url=ValueArgument('Project homepage', '--homepage'),
        description=ValueArgument('Describe the project', '--description'),
        max_members=IntArgument('Maximum subscribers', '--max-members'),
        private=BooleanArgument('True for private, False (default) for public',
                                '--private'),
        start_date=DateArgument('When to start the project', '--start-date'),
        end_date=DateArgument('When to end the project', '--end-date'),
        join_policy=PolicyArgument(
            'Set join policy (%s)' % ', '.join(PolicyArgument.policies),
            '--join-policy'),
        leave_policy=PolicyArgument(
            'Set leave policy (%s)' % ', '.join(PolicyArgument.policies),
            '--leave-policy'),
        resource_capacities=ProjectResourceArgument(
            'Set the member and project capacities for resources (repeatable) '
            'e.g., --resource cyclades.cpu=1,5    means "members will have at '
            'most 1 cpu but the project will have at most 5"       To see all '
            'resources:   kamaki resource list', '--resource'))
    required = ['specs_path', 'project_name', 'end_date']

    @errors.Generic.all
    @errors.Astakos.astakosclient
    @apply_notification
    def _run(self):
        specs = dict()
        if self['specs_path']:
            with open(abspath(self['specs_path'])) as f:
                specs = load(f)
        for key, arg in (('name', self['project_name']),
                         ('end_date', self.arguments['end_date'].isoformat),
                         ('start_date',
                          self.arguments['start_date'].isoformat),
                         ('owner', self['owner_uuid']), ('homepage',
                                                         self['homepage_url']),
                         ('description', self['description']),
                         ('max_members',
                          self['max_members']), ('private', self['private']),
                         ('join_policy',
                          self['join_policy']), ('leave_policy',
                                                 self['leave_policy']),
                         ('resources', self['resource_capacities'])):
            if arg:
                specs[key] = arg
        self.print_(self.client.create_project(specs), self.print_dict)

    def main(self):
        super(self.__class__, self)._run()
        self._req2 = [arg for arg in self.required if arg != 'specs_path']
        if not (self['specs_path'] or all(self[arg] for arg in self._req2)):
            raise CLIInvalidArgument(
                'Insufficient arguments',
                details=[
                    'Both of the following arguments are needed:', ', '.join(
                        [self.arguments[arg].lvalue for arg in self._req2]),
                    'OR provide a spec file (json) with %s' %
                    self.arguments['specs_path'].lvalue,
                    'OR combine arguments (higher priority) with a file'
                ])
        self._run()
Пример #7
0
class server_modify(_CycladesInit):
    """Modify attributes of a virtual server"""

    arguments = dict(
        server_name=ValueArgument('The new name', '--name'),
        flavor_id=IntArgument('Resize (set another flavor)', '--flavor-id'),
        firewall_profile=FirewallProfileArgument(
            'Valid values: %s' % (', '.join(FirewallProfileArgument.profiles)),
            '--firewall'),
        metadata_to_set=KeyValueArgument(
            'Set metadata in key=value form (can be repeated)',
            '--metadata-set'),
        metadata_to_delete=RepeatableArgument(
            'Delete metadata by key (can be repeated)', '--metadata-del'),
        public_network_port_id=ValueArgument(
            'Connection to set new firewall (* for all)', '--port-id'),
    )
    required = [
        'server_name', 'flavor_id', 'firewall_profile', 'metadata_to_set',
        'metadata_to_delete']

    def _set_firewall_profile(self, server_id):
        vm = self._restruct_server_info(
            self.client.get_server_details(server_id))
        ports = [p for p in vm['ports'] if 'firewallProfile' in p]
        pick_port = self.arguments['public_network_port_id']
        if pick_port.value:
            ports = [p for p in ports if pick_port.value in (
                '*', '%s' % p['id'])]
        elif len(ports) > 1:
            port_strings = ['Server %s ports to public networks:' % server_id]
            for p in ports:
                port_strings.append('  %s' % p['id'])
                for k in ('network_id', 'ipv4', 'ipv6', 'firewallProfile'):
                    v = p.get(k)
                    if v:
                        port_strings.append('\t%s: %s' % (k, v))
            raise CLIError(
                'Multiple public connections on server %s' % (
                    server_id), details=port_strings + [
                        'To select one:',
                        '  %s PORT_ID' % pick_port.lvalue,
                        'To set all:',
                        '  %s *' % pick_port.lvalue, ])
        if not ports:
            pp = pick_port.value
            raise CLIError(
                'No public networks attached on server %s%s' % (
                    server_id, ' through port %s' % pp if pp else ''),
                details=[
                    'To see all networks:', '  kamaki network list',
                    'To see all connections:',
                    '  kamaki server info %s --nics' % server_id,
                    'To connect to a network:',
                    '  kamaki network connect NETWORK_ID --device-id %s' % (
                        server_id)])
        for port in ports:
            self.error('Set port %s firewall to %s' % (
                port['id'], self['firewall_profile']))
            self.client.set_firewall_profile(
                server_id=server_id,
                profile=self['firewall_profile'],
                port_id=port['id'])

    def _server_is_stopped(self, server_id, cerror):
        vm = self.client.get_server_details(server_id)
        if vm['status'].lower() not in ('stopped'):
            raise CLIError(cerror, details=[
                'To resize a virtual server, it must be STOPPED',
                'Server %s status is %s' % (server_id, vm['status']),
                'To stop the server',
                '  kamaki server shutdown %s -w' % server_id])

    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.server_id
    def _run(self, server_id):
        if self['server_name'] is not None:
            self.client.update_server_name((server_id), self['server_name'])
        if self['flavor_id']:
            try:
                self.client.resize_server(server_id, self['flavor_id'])
            except ClientError as ce:
                if ce.status in (404, ):
                    self._flavor_exists(flavor_id=self['flavor_id'])
                if ce.status in (400, ):
                    self._server_is_stopped(server_id, ce)
                raise
        if self['firewall_profile']:
            self._set_firewall_profile(server_id)
        if self['metadata_to_set']:
            self.client.update_server_metadata(
                server_id, **self['metadata_to_set'])
        for key in (self['metadata_to_delete'] or []):
            errors.Cyclades.metadata(
                self.client.delete_server_metadata)(server_id, key=key)

    def main(self, server_id):
        super(self.__class__, self)._run()
        pnpid = self.arguments['public_network_port_id']
        fp = self.arguments['firewall_profile']
        if pnpid.value and not fp.value:
            raise CLIInvalidArgument('Invalid argument compination', details=[
                'Argument %s should always be combined with %s' % (
                    pnpid.lvalue, fp.lvalue)])
        self._run(server_id=server_id)
Пример #8
0
class server_create(_CycladesInit, OptionalOutput, _ServerWait):
    """Create a server (aka Virtual Machine)"""

    arguments = dict(
        server_name=ValueArgument('The name of the new server', '--name'),
        flavor_id=IntArgument('The ID of the flavor', '--flavor-id'),
        image_id=ValueArgument('The ID of the image', '--image-id'),
        key_name=ValueArgument('The name of the ssh key to add the server',
                               '--key-name'),
        personality=PersonalityArgument(
            (80 * ' ').join(howto_personality), ('-p', '--personality')),
        wait=FlagArgument('Wait server to build', ('-w', '--wait')),
        cluster_size=IntArgument(
            'Create a cluster of servers of this size. In this case, the name'
            'parameter is the prefix of each server in the cluster (e.g.,'
            'srv1, srv2, etc.',
            '--cluster-size'),
        max_threads=IntArgument(
            'Max threads in cluster mode (default 1)', '--threads'),
        network_configuration=NetworkArgument(
            'Connect server to network: [id=]NETWORK_ID[,[ip=]IP]        . '
            'Use only NETWORK_ID for private networks.        . '
            'Use NETWORK_ID,[ip=]IP for networks with IP.        . '
            'Can be repeated, mutually exclussive with --no-network',
            '--network'),
        no_network=FlagArgument(
            'Do not create any network NICs on the server.        . '
            'Mutually exclusive to --network        . '
            'If neither --network or --no-network are used, the default '
            'network policy is applied. These policies are set on the cloud, '
            'so kamaki is oblivious to them',
            '--no-network'),
        project_id=ValueArgument('Assign server to project', '--project-id'),
        metadata=KeyValueArgument(
            'Add custom metadata in key=value form (can be repeated). '
            'Overwrites metadata defined otherwise (i.e., image).',
            ('-m', '--metadata'))
    )
    required = ('server_name', 'flavor_id', 'image_id')

    @errors.Cyclades.cluster_size
    def _create_cluster(self, prefix, flavor_id, image_id, size):
        networks = self['network_configuration'] or (
            [] if self['no_network'] else None)
        servers = [dict(
            name='%s%s' % (prefix, i if size > 1 else ''),
            flavor_id=flavor_id,
            image_id=image_id,
            key_name=self['key_name'],
            project_id=self['project_id'],
            personality=self['personality'],
            metadata=self['metadata'],
            networks=networks) for i in range(1, 1 + size)]
        if size == 1:
            return [self.client.create_server(**servers[0])]
        self.client.MAX_THREADS = int(self['max_threads'] or 1)
        try:
            r = self.client.async_run(self.client.create_server, servers)
            return r
        except Exception as e:
            if size == 1:
                raise e
            try:
                requested_names = [s['name'] for s in servers]
                spawned_servers = [dict(
                    name=s['name'],
                    id=s['id']) for s in self.client.list_servers() if (
                        s['name'] in requested_names)]
                self.error('Failed to build %s servers' % size)
                self.error('Found %s matching servers:' % len(spawned_servers))
                self.print_(spawned_servers, out=self._err)
                self.error('Check if any of these servers should be removed')
            except Exception as ne:
                self.error('Error (%s) while notifying about errors' % ne)
            finally:
                raise e

    def _get_network_client(self):
        network = getattr(self, '_network_client', None)
        if not network:
            net_URL = self.astakos.get_endpoint_url(
                CycladesNetworkClient.service_type)
            network = CycladesNetworkClient(net_URL, self.client.token)
            self._network_client = network
        return network

    @errors.Image.id
    def _image_exists(self, image_id):
        self.client.get_image_details(image_id)

    @errors.Cyclades.network_id
    def _network_exists(self, network_id):
        network = self._get_network_client()
        network.get_network_details(network_id)

    def _ip_ready(self, ip, network_id, cerror):
        network = self._get_network_client()
        ips = [fip for fip in network.list_floatingips() if (
            fip['floating_ip_address'] == ip)]
        if not ips:
            msg = 'IP %s not available for current user' % ip
            raise CLIError(cerror, details=[msg] + errors.Cyclades.about_ips)
        ipnet, ipvm = ips[0]['floating_network_id'], ips[0]['instance_id']
        if getattr(cerror, 'status', 0) in (409, ):
            msg = ''
            if ipnet != network_id:
                msg = 'IP %s belong to network %s, not %s' % (
                    ip, ipnet, network_id)
            elif ipvm:
                msg = 'IP %s is already used by device %s' % (ip, ipvm)
            if msg:
                raise CLIError(cerror, details=[
                    msg,
                    'To get details on IP',
                    '  kamaki ip info %s' % ip] + errors.Cyclades.about_ips)

    @errors.Generic.all
    @errors.Cyclades.connection
    def _run(self):
        try:
            for r in self._create_cluster(
                    self['server_name'], self['flavor_id'], self['image_id'],
                    size=self['cluster_size'] or 1):
                if not r:
                    self.error('Create %s: server response was %s' % (
                        self['server_name'], r))
                    continue
                self.print_(r, self.print_dict)
                if self['wait']:
                    self.wait_while(r['id'], r['status'] or 'BUILD')
                self.writeln(' ')
        except ClientError as ce:
            if ce.status in (404, 400):
                self._flavor_exists(flavor_id=self['flavor_id'])
                self._image_exists(image_id=self['image_id'])
            if ce.status in (404, 400, 409):
                for net in self['network_configuration'] or []:
                    self._network_exists(network_id=net['uuid'])
                    if 'fixed_ip' in net:
                        self._ip_ready(net['fixed_ip'], net['uuid'], ce)
            if self['project_id'] and ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
            raise

    def main(self):
        super(self.__class__, self)._run()
        if self['no_network'] and self['network_configuration']:
            raise CLIInvalidArgument(
                'Invalid argument combination', importance=2, details=[
                    'Arguments %s and %s are mutually exclusive' % (
                        self.arguments['no_network'].lvalue,
                        self.arguments['network_configuration'].lvalue)])
        self._run()
Пример #9
0
class server_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
    """List virtual servers accessible by user
    Use filtering arguments (e.g., --name-like) to manage long server lists
    """

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
        since=DateArgument(
            'show only items modified since date (\'H:M:S YYYY-mm-dd\') '
            'Can look back up to a limit (POLL_TIME) defined on service-side',
            '--since'),
        limit=IntArgument(
            'limit number of listed virtual servers', ('-n', '--number')),
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
            '--more'),
        enum=FlagArgument('Enumerate results', '--enumerate'),
        flavor_id=ValueArgument('filter by flavor id', ('--flavor-id')),
        image_id=ValueArgument('filter by image id', ('--image-id')),
        user_id=ValueArgument('filter by user id', ('--user-id')),
        user_name=ValueArgument('filter by user name', ('--user-name')),
        status=ValueArgument(
            'filter by status (ACTIVE, STOPPED, REBOOT, ERROR, etc.)',
            ('--status')),
        meta=KeyValueArgument('filter by metadata key=values', ('--metadata')),
        meta_like=KeyValueArgument(
            'print only if in key=value, the value is part of actual value',
            ('--metadata-like')),
    )

    def _add_user_name(self, servers):
        uuids = self._uuids2usernames(list(set(
            [srv['user_id'] for srv in servers])))
        for srv in servers:
            srv['user_id'] += ' (%s)' % uuids[srv['user_id']]
        return servers

    def _apply_common_filters(self, servers):
        common_filters = dict()
        if self['status']:
            common_filters['status'] = self['status']
        if self['user_id'] or self['user_name']:
            uuid = self['user_id'] or self._username2uuid(self['user_name'])
            common_filters['user_id'] = uuid
        return filter_dicts_by_dict(servers, common_filters)

    def _filter_by_image(self, servers):
        iid = self['image_id']
        return [srv for srv in servers if srv['image']['id'] == iid]

    def _filter_by_flavor(self, servers):
        fid = self['flavor_id']
        return [srv for srv in servers if (
            '%s' % srv['flavor']['id'] == '%s' % fid)]

    def _filter_by_metadata(self, servers):
        new_servers = []
        for srv in servers:
            if 'metadata' not in srv:
                continue
            meta = [dict(srv['metadata'])]
            if self['meta']:
                meta = filter_dicts_by_dict(meta, self['meta'])
            if meta and self['meta_like']:
                meta = filter_dicts_by_dict(
                    meta, self['meta_like'], exact_match=False)
            if meta:
                new_servers.append(srv)
        return new_servers

    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.date
    def _run(self):
        withimage = bool(self['image_id'])
        withflavor = bool(self['flavor_id'])
        withmeta = bool(self['meta'] or self['meta_like'])
        withcommons = bool(
            self['status'] or self['user_id'] or self['user_name'])
        detail = self['detail'] or (
            withimage or withflavor or withmeta or withcommons)
        ch_since = self.arguments['since'].isoformat if self['since'] else None
        servers = list(self.client.list_servers(detail, ch_since) or [])

        servers = self._filter_by_name(servers)
        servers = self._filter_by_id(servers)
        servers = self._apply_common_filters(servers)
        if withimage:
            servers = self._filter_by_image(servers)
        if withflavor:
            servers = self._filter_by_flavor(servers)
        if withmeta:
            servers = self._filter_by_metadata(servers)

        if detail and self['detail']:
            pass
        else:
            for srv in servers:
                for key in set(srv).difference(['id', 'name']):
                    srv.pop(key)

        kwargs = dict(with_enumeration=self['enum'])
        if self['more']:
            codecinfo = codecs.lookup('utf-8')
            kwargs['out'] = codecs.StreamReaderWriter(
                cStringIO.StringIO(),
                codecinfo.streamreader,
                codecinfo.streamwriter)
            kwargs['title'] = ()
        if self['limit']:
            servers = servers[:self['limit']]
        self.print_(servers, **kwargs)
        if self['more']:
            pager(kwargs['out'].getvalue())

    def main(self):
        super(self.__class__, self)._run()
        self._run()
Пример #10
0
class imagecompute_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
    """List images"""
    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
        limit=IntArgument('limit number listed images', ('-n', '--number')),
        more=FlagArgument('handle long lists of results', '--more'),
        enum=FlagArgument('Enumerate results', '--enumerate'),
        user_id=ValueArgument('filter by user_id', '--user-id'),
        user_name=ValueArgument('filter by username', '--user-name'),
        meta=KeyValueArgument(
            'filter by metadata key=value (can be repeated)', ('--metadata')),
        meta_like=KeyValueArgument(
            'filter by metadata key=value (can be repeated)',
            ('--metadata-like'))
    )

    def _filter_by_metadata(self, images):
        new_images = []
        for img in images:
            meta = [dict(img['metadata'])]
            if self['meta']:
                meta = filter_dicts_by_dict(meta, self['meta'])
            if meta and self['meta_like']:
                meta = filter_dicts_by_dict(
                    meta, self['meta_like'], exact_match=False)
            if meta:
                new_images.append(img)
        return new_images

    def _filter_by_user(self, images):
        uuid = self['user_id'] or self._username2uuid(self['user_name'])
        return filter_dicts_by_dict(images, dict(user_id=uuid))

    def _add_name(self, images, key='user_id'):
        uuids = self._uuids2usernames(
            list(set([img[key] for img in images])))
        for img in images:
            img[key] += ' (%s)' % uuids[img[key]]
        return images

    @errors.Generic.all
    @errors.Cyclades.connection
    def _run(self):
        withmeta = bool(self['meta'] or self['meta_like'])
        withuser = bool(self['user_id'] or self['user_name'])
        detail = self['detail'] or withmeta or withuser
        images = self.client.list_images(detail)
        images = self._filter_by_name(images)
        images = self._filter_by_id(images)
        if withuser:
            images = self._filter_by_user(images)
        if withmeta:
            images = self._filter_by_metadata(images)
        if self['detail'] and not self['output_format']:
            images = self._add_name(self._add_name(images, 'tenant_id'))
        elif detail and not self['detail']:
            for img in images:
                for key in set(img).difference(['id', 'name']):
                    img.pop(key)
        kwargs = dict(with_enumeration=self['enum'])
        if self['limit']:
            images = images[:self['limit']]
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
        self.print_(images, **kwargs)
        if self['more']:
            pager(kwargs['out'].getvalue())

    def main(self):
        super(self.__class__, self)._run()
        self._run()
Пример #11
0
class image_register(_ImageInit, OptionalOutput):
    """(Re)Register an image file to an Image service
    The image file must be stored at a pithos repository
    Some metadata can be set by user (e.g., disk-format) while others are set
    by the system (e.g., image id).
    Custom user metadata are termed as "properties".
    A register command creates a remote meta file at
    /CONTAINER/IMAGE_PATH.meta
    Users may download and edit this file and use it to re-register.
    In case of a meta file, runtime arguments for metadata or properties
    override meta file settings.
    """
    container_info_cache = {}
    arguments = dict(
        checksum=ValueArgument('Set image checksum', '--checksum'),
        container_format=ValueArgument(
            'Set container format', '--container-format'),
        disk_format=ValueArgument('Set disk format', '--disk-format'),
        owner_name=ValueArgument('Set user uuid by user name', '--owner-name'),
        properties=KeyValueArgument(
            'Add property (user-specified metadata) in key=value form'
            '(can be repeated)',
            ('-p', '--property')),
        is_public=FlagArgument('Mark image as public', '--public'),
        size=IntArgument('Set image size in bytes', '--size'),
        metafile=ValueArgument(
            'Load metadata from a json-formated file IMAGE_FILE.meta :'
            '{"key1": "val1", "key2": "val2", ..., "properties: {...}"}',
            ('--metafile')),
        force_upload=FlagArgument(
            'Overwrite remote files (image file, metadata file)',
            ('-f', '--force')),
        no_metafile_upload=FlagArgument(
            'Do not store metadata in remote meta file',
            ('--no-metafile-upload')),
        container=ValueArgument(
            'Pithos+ container containing the image file',
            ('-C', '--container')),
        uuid=ValueArgument('Custom user uuid', '--uuid'),
        local_image_path=ValueArgument(
            'Local image file path to upload and register '
            '(still need target file in the form /CONTAINER/REMOTE-PATH )',
            '--upload-image-file'),
        progress_bar=ProgressBarArgument(
            'Do not use progress bar', '--no-progress-bar', default=False),
        name=ValueArgument('The name of the new image', '--name'),
        pithos_location=PithosLocationArgument(
            'The Pithos+ image location to put the image at. Format:       '
            'pithos://USER_UUID/CONTAINER/IMAGE_PATH             or   '
            '/CONTAINER/IMAGE_PATH',
            '--location')
    )
    required = ('name', 'pithos_location')

    def _get_pithos_client(self, locator):
        pithos = self.get_client(PithosClient, 'pithos')
        pithos.account, pithos.container = locator.uuid, locator.container
        return pithos

    def _load_params_from_file(self, location):
        params, properties = dict(), dict()
        pfile = self['metafile']
        if pfile:
            try:
                for k, v in load_image_meta(pfile).items():
                    key = k.lower().replace('-', '_')
                    if key == 'properties':
                        for pk, pv in v.items():
                            properties[pk.upper().replace('-', '_')] = pv
                    elif key == 'name':
                            continue
                    elif key == 'location':
                        if location:
                            continue
                        location = v
                    else:
                        params[key] = v
            except Exception as e:
                raiseCLIError(e, 'Invalid json metadata config file')
        return params, properties, location

    def _load_params_from_args(self, params, properties):
        for key in set([
                'checksum',
                'container_format',
                'disk_format',
                'owner',
                'size',
                'is_public']).intersection(self.arguments):
            params[key] = self[key]
        for k, v in self['properties'].items():
            properties[k.upper().replace('-', '_')] = v

    def _assert_remote_file_not_exist(self, pithos, path):
        if pithos and not self['force_upload']:
            try:
                pithos.get_object_info(path)
                raise CLIError('File already exists', importance=2, details=[
                    'A remote file /%s/%s already exists' % (
                        pithos.container, path),
                    'Use %s to force upload' % self.arguments[
                        'force_upload'].lvalue])
            except ClientError as ce:
                if ce.status != 404:
                    raise

    @errors.Generic.all
    @errors.Image.connection
    def _run(self, name, locator):
        location, pithos = locator.value, None
        if self['local_image_path']:
            with open(self['local_image_path']) as f:
                pithos = self._get_pithos_client(locator)
                self._assert_remote_file_not_exist(pithos, locator.path)
                (pbar, upload_cb) = self._safe_progress_bar('Uploading')
                if pbar:
                    hash_bar = pbar.clone()
                    hash_cb = hash_bar.get_generator('Calculating hashes')
                pithos.upload_object(
                    locator.path, f,
                    hash_cb=hash_cb, upload_cb=upload_cb,
                    container_info_cache=self.container_info_cache)
                pbar.finish()

        (params, properties, new_loc) = self._load_params_from_file(location)
        if location != new_loc:
            locator.value = new_loc
        self._load_params_from_args(params, properties)

        if not self['no_metafile_upload']:
            # check if metafile exists
            pithos = pithos or self._get_pithos_client(locator)
            meta_path = '%s.meta' % locator.path
            self._assert_remote_file_not_exist(pithos, meta_path)

        # register the image
        try:
            r = self.client.register(name, location, params, properties)
        except ClientError as ce:
            if ce.status in (400, 404):
                raise CLIError(
                    'Nonexistent image file location %s' % location,
                    details=[
                        '%s' % ce,
                        'Does the image file %s exist at container %s ?' % (
                            locator.path,
                            locator.container)] + howto_image_file)
            raise
        r['owner'] += ' (%s)' % self._uuid2username(r['owner'])
        self.print_(r, self.print_dict)

        # upload the metadata file
        if not self['no_metafile_upload']:
            try:
                meta_headers = pithos.upload_from_string(
                    meta_path, dumps(r, indent=2),
                    sharing=dict(read='*' if params.get('is_public') else ''),
                    container_info_cache=self.container_info_cache)
            except TypeError:
                self.error('Failed to dump metafile /%s/%s' % (
                    locator.container, meta_path))
                return
            if self['output_format']:
                self.print_json(dict(
                    metafile_location='/%s/%s' % (
                        locator.container, meta_path),
                    headers=meta_headers))
            else:
                self.error('Metadata file uploaded as /%s/%s (version %s)' % (
                    locator.container,
                    meta_path,
                    meta_headers['x-object-version']))

    def main(self):
        super(self.__class__, self)._run()
        locator, pithos = self.arguments['pithos_location'], None
        locator.setdefault('uuid', self.astakos.user_term('id'))
        locator.path = locator.path or path.basename(
            self['local_image_path'] or '')
        if not locator.path:
            raise CLIInvalidArgument(
                'Missing the image file or object', details=[
                    'Pithos+ URI %s does not point to a physical image' % (
                        locator.value),
                    'A physical image is necessary.',
                    'It can be a remote Pithos+ object or a local file.',
                    'To specify a remote image object:',
                    '  %s [pithos://UUID]/CONTAINER/PATH' % locator.lvalue,
                    'To specify a local file:',
                    '  %s [pithos://UUID]/CONTAINER[/PATH] %s LOCAL_PATH' % (
                        locator.lvalue,
                        self.arguments['local_image_path'].lvalue)])
        self.arguments['pithos_location'].setdefault(
            'uuid', self.astakos.user_term('id'))
        self._run(self['name'], locator)
Пример #12
0
class image_list(_ImageInit, OptionalOutput, NameFilter, IDFilter):
    """List images accessible by user"""

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
        container_format=ValueArgument(
            'filter by container format',
            '--container-format'),
        disk_format=ValueArgument('filter by disk format', '--disk-format'),
        size_min=IntArgument('filter by minimum size', '--size-min'),
        size_max=IntArgument('filter by maximum size', '--size-max'),
        status=ValueArgument('filter by status', '--status'),
        owner=ValueArgument('filter by owner', '--owner'),
        owner_name=ValueArgument('filter by owners username', '--owner-name'),
        order=ValueArgument(
            'order by FIELD ( - to reverse order)',
            '--order',
            default=''),
        limit=IntArgument('limit number of listed images', ('-n', '--number')),
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
            '--more'),
        enum=FlagArgument('Enumerate results', '--enumerate'),
        prop=KeyValueArgument('filter by property key=value', ('--property')),
        prop_like=KeyValueArgument(
            'fliter by property key=value where value is part of actual value',
            ('--property-like')),
        image_ID_for_members=ValueArgument(
            'List members of an image', '--members-of'),
    )

    def _filter_by_owner(self, images):
        ouuid = self['owner'] or self._username2uuid(self['owner_name'])
        return filter_dicts_by_dict(images, dict(owner=ouuid))

    def _add_owner_name(self, images):
        uuids = self._uuids2usernames(
            list(set([img['owner'] for img in images])))
        for img in images:
            img['owner'] += ' (%s)' % uuids[img['owner']]
        return images

    def _filter_by_properties(self, images):
        new_images = []
        for img in images:
            props = [dict(img['properties'])]
            if self['prop']:
                props = filter_dicts_by_dict(props, self['prop'])
            if props and self['prop_like']:
                props = filter_dicts_by_dict(
                    props, self['prop_like'], exact_match=False)
            if props:
                new_images.append(img)
        return new_images

    def _members(self, image_id):
        members = self.client.list_members(image_id)
        if not self['output_format']:
            uuids = [member['member_id'] for member in members]
            usernames = self._uuids2usernames(uuids)
            for member in members:
                member['member_id'] += ' (%s)' % usernames[member['member_id']]
        self.print_(members, title=('member_id',))

    @errors.Generic.all
    @errors.Cyclades.connection
    def _run(self):
        super(self.__class__, self)._run()
        if self['image_ID_for_members']:
            return self._members(self['image_ID_for_members'])
        filters = {}
        for arg in set([
                'container_format',
                'disk_format',
                'name',
                'size_min',
                'size_max',
                'status']).intersection(self.arguments):
            filters[arg] = self[arg]

        order = self['order']
        detail = any([self[x] for x in (
            'detail', 'prop', 'prop_like', 'owner', 'owner_name')])

        images = self.client.list_public(detail, filters, order)

        if self['owner'] or self['owner_name']:
            images = self._filter_by_owner(images)
        if self['prop'] or self['prop_like']:
            images = self._filter_by_properties(images)
        images = self._filter_by_id(images)
        images = self._non_exact_name_filter(images)
        for img in [] if self['output_format'] else images:
            try:
                img['size'] = format_size(img['size'])
            except KeyError:
                continue

        if self['detail'] and not self['output_format']:
            images = self._add_owner_name(images)
        elif detail and not self['detail']:
            for img in images:
                for key in set(img).difference([
                        'id',
                        'name',
                        'status',
                        'container_format',
                        'disk_format',
                        'size']):
                    img.pop(key)
        kwargs = dict(with_enumeration=self['enum'])
        if self['limit']:
            images = images[:self['limit']]
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
        self.print_(images, **kwargs)
        if self['more']:
            pager(kwargs['out'].getvalue())

    def main(self):
        super(self.__class__, self)._run()
        self._run()