class port_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter): """List all ports""" arguments = dict(detail=FlagArgument('show detailed output', ('-l', '--details')), more=FlagArgument('output results in pages', '--more'), user_id=ValueArgument( 'show only networks belonging to user with this id', '--user-id')) @errors.Generic.all @errors.Cyclades.connection def _run(self): ports = self.client.list_ports() ports = self._filter_by_user_id(ports) ports = self._filter_by_name(ports) ports = self._filter_by_id(ports) if not self['detail']: ports = [dict(id=p['id'], name=p['name']) for p in ports] kwargs = dict() if self['more']: kwargs['out'] = StringIO() kwargs['title'] = () self.print_(ports, **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
class commission_issue(_AstakosInit, OptionalOutput): """Issue commissions as a json string (special privileges required)""" arguments = dict(uuid=ValueArgument('User UUID', '--uuid'), source=ValueArgument('Commission source (ex system)', '--source'), file_path=ValueArgument('File of provisions', '--provisions-file'), description=ValueArgument('Commision description', '--description'), force=FlagArgument('Force commission', '--force'), accept=FlagArgument('Do not wait for verification', '--accept')) required = ('uuid', 'source', 'file_path') @errors.Generic.all @errors.Astakos.astakosclient def _run(self): try: with open(self['file_path']) as f: provisions = loads(f.read()) except Exception as e: raise CLIError('Failed load a json dict from file %s' % self['file_path'], importance=2, details=['%s' % e]) self.print_( self.client.issue_one_commission(self['uuid'], self['source'], provisions, self['description'] or '', self['force'], self['accept'])) def main(self): super(self.__class__, self)._run() self._run()
class subnet_create(_NetworkInit, OptionalOutput): """Create a new subnet""" arguments = dict( name=ValueArgument('Subnet name', '--name'), allocation_pools=AllocationPoolArgument( 'start_address,end_address of allocation pool (can be repeated)' ' e.g., --alloc-pool=123.45.67.1,123.45.67.8', '--alloc-pool'), gateway=ValueArgument('Gateway IP', '--gateway'), subnet_id=ValueArgument('The id for the subnet', '--id'), ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'), enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'), network_id=ValueArgument('Set the network ID', '--network-id'), cidr=ValueArgument('Set the CIDR', '--cidr')) required = ('network_id', 'cidr') @errors.Generic.all @errors.Cyclades.connection def _run(self): try: net = self.client.create_subnet(self['network_id'], self['cidr'], self['name'], self['allocation_pools'], self['gateway'], self['subnet_id'], self['ipv6'], self['enable_dhcp']) except ClientError as ce: if ce.status in (404, 400): self._network_exists(network_id=self['network_id']) raise self.print_(net, self.print_dict) def main(self): super(self.__class__, self)._run() self._run()
class subnet_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter): """List subnets Use filtering arguments (e.g., --name-like) to manage long server lists """ arguments = dict(detail=FlagArgument('show detailed output', ('-l', '--details')), more=FlagArgument('output results in pages', '--more')) @errors.Generic.all @errors.Cyclades.connection def _run(self): nets = self.client.list_subnets() nets = self._filter_by_name(nets) nets = self._filter_by_id(nets) if not self['detail']: nets = [ dict(id=n['id'], name=n['name'], net='( of network %s )' % n['network_id']) for n in nets ] kwargs = dict(title=('id', 'name', 'net')) else: kwargs = dict() if self['more']: kwargs['out'] = StringIO() kwargs['title'] = () self.print_(nets, **kwargs) if self['more']: pager('%s' % kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
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()
class image_modify(_ImageInit): """Add / update metadata and properties for an image Preserves values not explicitly modified """ arguments = dict( image_name=ValueArgument('Change name', '--name'), disk_format=ValueArgument('Change disk format', '--disk-format'), container_format=ValueArgument('Change container format', '--container-format'), status=ValueArgument('Change status', '--status'), publish=FlagArgument('Make the image public', '--public'), unpublish=FlagArgument('Make the image private', '--private'), property_to_set=KeyValueArgument( 'set property in key=value form (can be repeated)', ('-p', '--property-set')), property_to_del=RepeatableArgument( 'Delete property by key (can be repeated)', '--property-del'), member_ID_to_add=RepeatableArgument( 'Add member to image (can be repeated)', '--member-add'), member_ID_to_remove=RepeatableArgument( 'Remove a member (can be repeated)', '--member-del'), ) required = [ 'image_name', 'disk_format', 'container_format', 'status', 'publish', 'unpublish', 'property_to_set', 'member_ID_to_add', 'member_ID_to_remove', 'property_to_del' ] @errors.Generic.all @errors.Image.connection @errors.Image.permissions @errors.Image.id def _run(self, image_id): for mid in (self['member_ID_to_add'] or []): self.client.add_member(image_id, mid) for mid in (self['member_ID_to_remove'] or []): self.client.remove_member(image_id, mid) meta = self.client.get_meta(image_id) for k, v in self['property_to_set'].items(): meta['properties'][k.upper()] = v for k in (self['property_to_del'] or []): meta['properties'][k.upper()] = None self.client.update_image(image_id, name=self['image_name'], disk_format=self['disk_format'], container_format=self['container_format'], status=self['status'], public=self['publish'] or (False if self['unpublish'] else None), **meta['properties']) def main(self, image_id): super(self.__class__, self)._run() self._run(image_id=image_id)
class config_list(CommandInit): """List all configuration options FAQ: Q: I haven't set any options! A: Defaults are used (override with /config set ) Q: There are more options than I have set A: Default options remain if not explicitly replaced or deleted """ arguments = dict( describe_options=FlagArgument( 'List all option keys, if known to kamaki, including inactive ' 'ones', '--describe-option-keys'), with_description=FlagArgument('Add description to listed options', '--with-description'), ) @errors.Generic.all def _run(self): if self['describe_options']: for group, options in DOCUMENTATION.items(): self.writeln() for k, v in options.items(): self.writeln('%s.%s: \t%s' % (group, k, v[0])) else: for section in sorted(self.config.sections()): items = self.config.items(section) for key, val in sorted(items): if section in ('cloud', ): prefix = '%s.%s' % (section, key) for k, v in val.items(): if self['with_description']: try: self.writeln( '# %s' % DOCUMENTATION['cloud.<CLOUD NAME>'][k]) except KeyError: self.writeln('# unknown option') self.writeln('%s.%s %s' % (prefix, k, v)) self.writeln() else: if self['with_description']: try: self.writeln('# %s' % DOCUMENTATION[section][key]) except KeyError: self.writeln('# unknown option') self.writeln('%s.%s %s' % (section, key, val)) def main(self): self._run()
class port_create(_port_create): """Create a new port (== connect server to network)""" arguments = dict( name=ValueArgument('A human readable name', '--name'), security_group_id=RepeatableArgument( 'Add a security group id (can be repeated)', ('-g', '--security-group')), subnet_id=ValueArgument( 'Subnet id for fixed ips (used with --ip-address)', '--subnet-id'), ip_address=ValueArgument('IP address for subnet id', '--ip-address'), network_id=ValueArgument('Set the network ID', '--network-id'), device_id=ValueArgument( 'The device is either a virtual server or a virtual router', '--device-id'), wait=FlagArgument('Wait port to be established', ('-w', '--wait')), ) required = ('network_id', 'device_id') @errors.Generic.all @errors.Cyclades.connection def _run(self): self.connect(self['network_id'], self['device_id']) def main(self): super(self.__class__, self)._run() self._run()
class network_create(_NetworkInit, OptionalOutput): """Create a new network (default type: MAC_FILTERED)""" arguments = dict( name=ValueArgument('Network name', '--name'), shared=FlagArgument( 'Make network shared (special privileges required)', '--shared'), project_id=ValueArgument('Assign network to project', '--project-id'), network_type=NetworkTypeArgument( 'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)), '--type')) @errors.Generic.all @errors.Cyclades.connection def _run(self): try: net = self.client.create_network(self['network_type'], name=self['name'], shared=self['shared'], project_id=self['project_id']) except ClientError as ce: if self['project_id'] and ce.status in (400, 403, 404): self._project_id_exists(project_id=self['project_id']) raise self.print_(net, self.print_dict) def main(self): super(self.__class__, self)._run() self._run()
class config_delete(CommandInit): """Delete a configuration option Default values are not removed by default. To alter this behavior in a session, use --default. """ arguments = dict(default=FlagArgument( 'Remove default value as well (persists until end of session)', '--default')) @errors.Generic.all def _run(self, option): section, sep, key = option.rpartition('.') section = section or 'global' prefix = 'cloud.' if section.startswith(prefix): cloud = section[len(prefix):] try: self.config.remove_from_cloud(cloud, key) except KeyError: raise CLIError('Field %s does not exist' % option) else: self.config.remove_option(section, key, self['default']) self.config.write() self.config.reload() def main(self, option): self._run(option)
class image_info(_ImageInit, OptionalOutput): """Get image metadata""" arguments = dict(hashmap=FlagArgument( 'Get image file hashmap instead of metadata', '--hashmap'), ) @errors.Generic.all @errors.Image.connection @errors.Image.id def _run(self, image_id): meta = self.client.get_meta(image_id) if self['hashmap']: print meta['location'] location = meta['location'].split('pithos://')[1] location = location.split('/') uuid, container = location[0], location[1] pithos = self.get_client(PithosClient, 'pithos') pithos.account, pithos.container = uuid, container path = '/'.join(location[2:]) meta = pithos.get_object_hashmap(path) elif not self['output_format']: try: meta['owner'] += ' (%s)' % self._uuid2username(meta['owner']) except KeyError as ke: log.debug('%s' % ke) try: meta['size'] = format_size(meta['size']) except KeyError as ke: log.debug('%s' % ke) self.print_(meta, self.print_dict) def main(self, image_id): super(self.__class__, self)._run() self._run(image_id=image_id)
class project_list(_AstakosInit, OptionalOutput): """List all projects""" arguments = dict( details=FlagArgument('Show details', ('-l', '--details')), name=ValueArgument('Filter by name', ('--with-name', )), state=ValueArgument('Filter by state', ('--with-state', )), owner=ValueArgument('Filter by owner', ('--with-owner', )), ) @errors.Generic.all @errors.Astakos.astakosclient def _run(self): r = self.client.get_projects(self['name'], self['state'], self['owner']) if not (self['details'] or self['output_format']): r = [ dict(id=i['id'], name=i['name'], description=i['description']) for i in r ] self.print_(r) def main(self): super(self.__class__, self)._run() self._run()
class server_reboot(_CycladesInit, _ServerWait): """Reboot a virtual server""" arguments = dict( type=ValueArgument('SOFT or HARD - default: SOFT', ('--type')), wait=FlagArgument('Wait server to start again', ('-w', '--wait')) ) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): hard_reboot = None if self['type']: if self['type'].lower() in ('soft', ): hard_reboot = False elif self['type'].lower() in ('hard', ): hard_reboot = True else: raise CLISyntaxError( 'Invalid reboot type %s' % self['type'], importance=2, details=[ '--type values are either SOFT (default) or HARD']) self.client.reboot_server(int(server_id), hard_reboot) if self['wait']: self.wait_while(server_id, 'REBOOT') def main(self, server_id): super(self.__class__, self)._run() self._run(server_id=server_id)
class network_connect(_port_create): """Connect a network with a device (server or router)""" arguments = dict( name=ValueArgument('A human readable name for the port', '--name'), security_group_id=RepeatableArgument( 'Add a security group id (can be repeated)', ('-g', '--security-group')), subnet_id=ValueArgument( 'Subnet id for fixed ips (used with --ip-address)', '--subnet-id'), ip_address=ValueArgument( 'IP address for subnet id (used with --subnet-id', '--ip-address'), wait=FlagArgument('Wait network to connect', ('-w', '--wait')), device_id=RepeatableArgument( 'Connect this device to the network (can be repeated)', '--device-id')) required = ('device_id', ) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.network_id def _run(self, network_id, server_id): self.error('Creating a port to connect network %s with device %s' % (network_id, server_id)) try: self.connect(network_id, server_id) except ClientError as ce: if ce.status in (400, 404): self._server_exists(server_id=server_id) raise def main(self, network_id): super(self.__class__, self)._run() for sid in self['device_id']: self._run(network_id=network_id, server_id=sid)
class ip_detach(_NetworkInit, _PortWait, OptionalOutput): """Detach an IP from a virtual server""" arguments = dict(wait=FlagArgument('Wait until IP is detached', ('-w', '--wait')), ) @errors.Generic.all @errors.Cyclades.connection def _run(self, ip_or_ip_id): for ip in self.client.list_floatingips(): if ip_or_ip_id in (ip['floating_ip_address'], ip['id']): if not ip['port_id']: raiseCLIError('IP %s is not attached' % ip_or_ip_id) self.error('Deleting port %s:' % ip['port_id']) self.client.delete_port(ip['port_id']) if self['wait']: port_status = self.client.get_port_details( ip['port_id'])['status'] try: self.wait_while(ip['port_id'], port_status) except ClientError as ce: if ce.status not in (404, ): raise self.error('Port %s is deleted' % ip['port_id']) return raiseCLIError('IP or IP id %s not found' % ip_or_ip_id) def main(self, ip_or_ip_id): super(self.__class__, self)._run() self._run(ip_or_ip_id)
class server_delete(_CycladesInit, _ServerWait): """Delete a virtual server""" arguments = dict( wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait')), cluster=FlagArgument( '(DANGEROUS) Delete all VMs with names starting with the cluster ' 'prefix. Do not use it if unsure. Syntax:' ' kamaki server delete --cluster CLUSTER_PREFIX', '--cluster')) def _server_ids(self, server_var): if self['cluster']: return [ s['id'] for s in self.client.list_servers() if (s['name'].startswith(server_var)) ] return [ server_var, ] @errors.Cyclades.server_id def _delete_server(self, server_id): if self['wait']: details = self.client.get_server_details(server_id) status = details['status'] self.client.delete_server(server_id) if self['wait']: self.wait(server_id, status) @errors.Generic.all @errors.Cyclades.connection def _run(self, server_var): deleted_vms = [] for server_id in self._server_ids(server_var): self._delete_server(server_id=server_id) deleted_vms.append(server_id) if self['cluster']: dlen = len(deleted_vms) self.error('%s virtual server %s deleted' % (dlen, '' if dlen == 1 else 's')) def main(self, server_id_or_cluster_prefix): super(self.__class__, self)._run() self._run(server_id_or_cluster_prefix)
class network_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter): """List networks Use filtering arguments (e.g., --name-like) to manage long lists """ arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--details')), more=FlagArgument( 'output results in pages (-n to set items per page, default 10)', '--more'), user_id=ValueArgument( 'show only networks belonging to user with this id', '--user-id')) @errors.Generic.all @errors.Cyclades.connection def _run(self): nets = self.client.list_networks(detail=True) nets = self._filter_by_user_id(nets) nets = self._filter_by_name(nets) nets = self._filter_by_id(nets) if not self['detail']: nets = [ dict(id=n['id'], name=n['name'], public='( %s )' % ('public' if (n.get('public', None)) else 'private')) for n in nets ] kwargs = dict(title=('id', 'name', 'public')) else: kwargs = dict() if self['more']: kwargs['out'] = StringIO() kwargs['title'] = () self.print_(nets, **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
class server_info(_CycladesInit, OptionalOutput): """Detailed information on a Virtual Machine""" arguments = dict(nics=FlagArgument( 'Show only the network interfaces of this virtual server', '--nics'), stats=FlagArgument('Get URLs for server statistics', '--stats'), diagnostics=FlagArgument('Diagnostic information', '--diagnostics')) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): if self['nics']: self.print_(self.client.get_server_nics(server_id), self.print_dict) elif self['stats']: self.print_(self.client.get_server_stats(server_id), self.print_dict) elif self['diagnostics']: self.print_(self.client.get_server_diagnostics(server_id)) else: vm = self.client.get_server_details(server_id) self.print_(vm, self.print_dict) def main(self, server_id): super(self.__class__, self)._run() choose_one = ('nics', 'stats', 'diagnostics') count = len([a for a in choose_one if self[a]]) if count > 1: raise CLIInvalidArgument( 'Invalid argument combination', details=[ 'Arguments %s cannot be used simultaneously' % ', '.join([self.arguments[a].lvalue for a in choose_one]) ]) self._run(server_id=server_id)
class keypair_upload(_CycladesInit, OptionalOutput): """Upload or update a keypair""" arguments = dict( key_name=ValueArgument( 'The name of the key', '--key-name'), public_key=ValueArgument( 'The contents of the keypair(the public key)', '--public-key'), force=FlagArgument( '(DANGEROUS) Force keypair creation. This option' ' will delete an existing keypair in case of a name' 'conflict. Do not use it if unsure.', ('-f', '--force') ) ) required = ['public_key'] @errors.Generic.all @errors.Keypair.connection def _run(self): key_name = self['key_name'] if key_name is None: uniq = str(uuid.uuid4())[:8] key_name = 'kamaki-key_autogen_{:%m_%d_%H_%M_%S_%f}_{uniq}'.format( datetime.now(), uniq=uniq) try: keypair = self.client.create_key( key_name=key_name, public_key=self['public_key']) except ClientError as e: if e.status == 409 and self['force']: self.client.delete_keypair(key_name) keypair = self.client.create_key( key_name=key_name, public_key=self['public_key']) else: help_command = ('kamaki keypair upload -f --key-name %s ' '--public-key %s' % (key_name, self['public_key'])) self._err.write('A keypair with that name already exists. ' 'To override the conflicting key you can use ' 'the following command:\n%s\n' % (help_command)) raise e self.print_(keypair, self.print_dict) def main(self): super(self.__class__, self)._run() self._run()
class quota_list(_AstakosInit, OptionalOutput): """Show user quotas""" _to_format = set(['cyclades.disk', 'pithos.diskspace', 'cyclades.ram']) arguments = dict(resource=ValueArgument('Filter by resource', '--resource'), project_id=ValueArgument('Filter by project', '--project-id'), bytes=FlagArgument('Show data size in bytes', '--bytes')) def _print_quotas(self, quotas, *args, **kwargs): if not self['bytes']: for project_id, resources in quotas.items(): for r in self._to_format.intersection(resources): resources[r] = dict([(k, format_size(v)) for k, v in resources[r].items()]) self.print_dict(quotas, *args, **kwargs) @errors.Generic.all @errors.Astakos.astakosclient def _run(self): quotas = self.client.get_quotas() if self['project_id']: try: resources = quotas[self['project_id']] except KeyError: raise CLIError('User not assigned to project with id "%s" ' % (self['project_id']), details=[ 'See all quotas of current user:'******' kamaki quota list' ]) quotas = {self['project_id']: resources} if self['resource']: d = dict() for project_id, resources in quotas.items(): r = dict() for resource, value in resources.items(): if (resource.startswith(self['resource'])): r[resource] = value if r: d[project_id] = r if not d: raise CLIError('Resource "%s" not found' % self['resource']) quotas = d self.print_(quotas, self._print_quotas) def main(self): super(self.__class__, self)._run() self._run()
class network_disconnect(_NetworkInit, _PortWait, OptionalOutput): """Disconnect a network from a device""" arguments = dict( wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')), device_id=RepeatableArgument( 'Disconnect device from the network (can be repeated)', '--device-id')) required = ('device_id', ) @errors.Cyclades.server_id def _get_vm(self, server_id): return self._get_compute_client().get_server_details(server_id) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.network_id def _run(self, network_id, server_id): vm = self._get_vm(server_id=server_id) ports = [ port for port in vm['attachments'] if (port['network_id'] in (network_id, )) ] if not ports: raiseCLIError('Device %s has no network %s attached' % (server_id, network_id), importance=2, details=[ 'To get device networking', ' kamaki server info %s --nics' % server_id ]) for port in ports: if self['wait']: port['status'] = self.client.get_port_details( port['id'])['status'] self.client.delete_port(port['id']) self.error('Deleting port %s (net-id: %s, device-id: %s):' % (port['id'], network_id, server_id)) if self['wait']: try: self.wait_while(port['id'], port['status']) except ClientError as ce: if ce.status not in (404, ): raise self.error('Port %s is deleted' % port['id']) def main(self, network_id): super(self.__class__, self)._run() for sid in self['device_id']: self._run(network_id=network_id, server_id=sid)
class user_list(_AstakosInit, OptionalOutput): """List (cached) session users""" arguments = dict( detail=FlagArgument('Detailed listing', ('-l', '--detail')) ) @errors.Generic.all @errors.Astakos.astakosclient def _run(self): self.print_([u if self['detail'] else (dict( id=u['id'], name=u['name'])) for u in self.astakos.list_users()]) def main(self): super(self.__class__, self)._run() self._run()
class ip_attach(_port_create): """Attach an IP on a virtual server""" arguments = dict(name=ValueArgument('A human readable name for the port', '--name'), security_group_id=RepeatableArgument( 'Add a security group id (can be repeated)', ('-g', '--security-group')), subnet_id=ValueArgument('Subnet id', '--subnet-id'), wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')), server_id=ValueArgument('Server to attach to this IP', '--server-id')) required = ('server_id', ) @errors.Generic.all @errors.Cyclades.connection def _run(self, ip_or_ip_id): netid = None for ip in self.client.list_floatingips(): if ip_or_ip_id in (ip['floating_ip_address'], ip['id']): netid = ip['floating_network_id'] iparg = ValueArgument(parsed_name='--ip') iparg.value = ip['floating_ip_address'] self.arguments['ip_address'] = iparg break if netid: server_id = self['server_id'] self.error('Creating a port to attach IP %s to server %s' % (ip_or_ip_id, server_id)) try: self.connect(netid, server_id) except ClientError as ce: self.error('Failed to connect network %s with server %s' % (netid, server_id)) if ce.status in (400, 404): self._server_exists(server_id=server_id) self._network_exists(network_id=netid) raise else: raiseCLIError('%s does not match any reserved IPs or IP ids' % ip_or_ip_id, details=errors.Cyclades.about_ips) def main(self, ip_or_ip_id): super(self.__class__, self)._run() self._run(ip_or_ip_id=ip_or_ip_id)
class server_shutdown(_CycladesInit, _ServerWait): """Shutdown an active virtual server""" arguments = dict( wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): status = self.assert_not_in_status(server_id, 'STOPPED') self.client.shutdown_server(int(server_id)) if self['wait']: self.wait(server_id, status) def main(self, server_id): super(self.__class__, self)._run() self._run(server_id=server_id)
class server_start(_CycladesInit, _ServerWait): """Start an existing virtual server""" arguments = dict( wait=FlagArgument('Wait server to start', ('-w', '--wait')) ) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): status = self.assert_not_in_status(server_id, 'ACTIVE') self.client.start_server(int(server_id)) if self['wait']: self.wait_while(server_id, status) def main(self, server_id): super(self.__class__, self)._run() self._run(server_id=server_id)
class keypair_list(_CycladesInit, OptionalOutput, NameFilter): """List all keypairs""" arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--detail')), ) @errors.Generic.all @errors.Keypair.connection def _run(self): keypairs = self.client.list_keypairs() keypairs = [k['keypair'] for k in keypairs] if not self['detail']: remove_from_items(keypairs, 'public_key') keypairs = self._filter_by_name(keypairs) self.print_(keypairs) def main(self): super(self.__class__, self)._run() self._run()
class port_delete(_NetworkInit, _PortWait): """Delete a port (== disconnect server from network)""" arguments = dict( wait=FlagArgument('Wait port to be deleted', ('-w', '--wait'))) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.port_id def _run(self, port_id): if self['wait']: status = self.client.get_port_details(port_id)['status'] self.client.delete_port(port_id) if self['wait']: try: self.wait_while(port_id, status) except ClientError as ce: if ce.status not in (404, ): raise self.error('Port %s is deleted' % port_id) def main(self, port_id): super(self.__class__, self)._run() self._run(port_id=port_id)
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)
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()
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()