class GeneralConfig(FiggisConfig): conf_dir = Field(expand, default='$HOME/.suggestive') database = Field(expand, default='{conf_dir}/music.db') highcolor = Field(bool, default=True) default_buffers = Field(CSV, valid_buffers, default=['library', 'playlist']) orientation = Field(default='horizontal', choices=VALID_ORIENTATIONS) log = Field(expand, default='{conf_dir}/log.txt') verbose = Field(bool, default=False) log_sql_queries = Field(bool, default=False) session_file = Field(expand, default='{conf_dir}/session') update_on_startup = Field(bool, default=False) @property def colormode(self): return 256 if self.highcolor else 88 @property def log_level(self): return logging.DEBUG if self.verbose else logging.INFO @property def sqlalchemy_url(self): return 'sqlite:///{}'.format(self.database)
class ClusterCreateNodeGroups(Config): __allow_extra__ = False id = Field(six.text_type, required=True, validator=Length(min=1, max=255)) count = Field(int, validator=Range(min=1, max=100)) flavor_id = Field(six.text_type)
class AbsoluteLimit(Config, ReprMixin): limit = Field(int, required=True) remaining = Field(int, required=True) @property def used(self): return self.limit - self.remaining
class PlaylistConfig(FiggisConfig): __nointerpolate__ = {'status_format'} status_format = Field( default='{status}: {artist} - {title} [{time_elapsed}/{time_total}]') save_playlist_on_close = Field(bool, default=False) playlist_save_name = Field(default='suggestive.state')
class Size(Config, ReprMixin): table_columns = ('flavor', 'minutes', 'nodecount', 'recommended') table_header = ('Flavor', 'Minutes', 'Nodes', 'Recommended') flavor_id = Field(six.text_type, required=True, key='flavor') minutes = Field(float, required=True) nodecount = Field(int, required=True) recommended = Field(bool, default=False)
class NodeGroup(Config, ReprMixin): """Group of nodes that share the same flavor and installed services""" table_columns = ('id', 'flavor_id', 'count', '_components') table_header = ('ID', 'Flavor', 'Count', 'Components') id = Field(six.text_type, required=True, validator=Length(min=1, max=255)) count = Field(int, validator=Range(min=1, max=100)) flavor_id = Field(six.text_type) components = ListField(dict, default={})
class CreateS3Request(Config): access_key_id = Field(six.text_type, required=True, nullable=False, validator=Length(min=20, max=20)) access_secret_key = Field(six.text_type, nullable=False, required=True, validator=Length(min=40, max=40))
class CreateCloudFilesRequest(Config): username = Field(six.text_type, required=True, nullable=False, validator=Length(min=3, max=255)) api_key = Field(six.text_type, nullable=False, required=True, validator=Length(min=20, max=40))
class CreateAmbariRequest(Config): username = Field(six.text_type, required=True, nullable=False, validator=Length(min=3, max=255)) password = Field(six.text_type, nullable=False, required=True, validator=Length(min=8, max=255))
class CreateSSHKeyRequest(Config): key_name = Field(six.text_type, required=True, nullable=False, validator=Length(min=3, max=255)) public_key = Field(six.text_type, nullable=False, required=True, validator=Length(min=50, max=1024))
class NodeGroup(Config): id = Field(six.text_type, required=True, validator=Length(min=1, max=36)) flavor_id = Field(six.text_type) count = Field(int, validator=Range(min=0, max=100)) components = ListField(Component) @classmethod def _describe(cls): return cls.describe().replace('\n', ', ')
class CreateStackRequest(Config): name = Field(six.text_type, required=True, validator=Length(min=1, max=255)) description = Field(six.text_type, validator=Length(min=1, max=1024)) distro = Field(six.text_type, required=True, validator=Length(min=1, max=255)) services = ListField(Service, required=True) node_groups = ListField(NodeGroup)
class Recommendations(Config, ReprMixin): """Recommendations on how to use the Lava API for a given workload""" name = Field(six.text_type, required=True) description = Field(six.text_type, required=True) requires = ListField(six.text_type, required=True) sizes = ListField(Size, required=True, help='See: :class:`Size`') @property def _description(self): return '\n'.join(textwrap.wrap(self.description, 30))
class Flavor(Config, ReprMixin): table_columns = ('id', 'name', 'ram', 'vcpus', 'disk') table_header = ('ID', 'Name', 'RAM', 'VCPUs', 'Disk') id = Field(six.text_type, required=True) name = Field(six.text_type, required=True) disk = Field(int, required=True, help='Disk space in MB') vcpus = Field(int, required=True) ram = Field(int, required=True, help='Memory in MB') links = ListField(Link)
class DistroService(Config, ReprMixin): table_columns = ('name', 'version', '_components', '_description') table_header = ('Name', 'Version', 'Components', 'Description') name = Field(six.text_type, required=True) version = Field(six.text_type, required=True) description = Field(six.text_type, required=True) components = ListField(dict, required=True) @property def _description(self): return '\n'.join(textwrap.wrap(self.description, 30))
class StackNodeGroup(Config, ReprMixin): table_columns = ('id', 'flavor_id', 'count', 'resource_limits.min_ram', 'resource_limits.min_count', 'resource_limits.max_count') table_header = ('ID', 'Flavor', 'Count', 'Min RAM', 'Min count', 'Max Count') id = Field(six.text_type, required=True) flavor_id = Field(six.text_type, required=True) resource_limits = Field(ResourceLimits, required=True, help='See: :class:`ResourceLimits`') count = Field(int, required=True) components = ListField(dict, required=True)
class ClusterCreateRequest(Config): """POST data to create cluster""" name = Field(six.text_type, required=True, validator=Length(min=1, max=255)) username = Field(six.text_type, required=True, validator=[Length(min=2, max=255), valid_username]) ssh_keys = ListField(six.text_type, required=True, validator=List(Length(min=1, max=255))) stack_id = Field(six.text_type, required=True) node_groups = ListField(ClusterCreateNodeGroups) scripts = ListField(ClusterCreateScript) connectors = ListField(ClusterCreateCredential) credentials = ListField(ClusterCreateCredential)
class Script(Config, ReprMixin): table_columns = ('id', 'name', 'type', 'is_public', 'created', 'url') table_header = ('ID', 'Name', 'Type', 'Public', 'Created', 'URL') id = Field(six.text_type, required=True) name = Field(six.text_type, required=True) type = Field(six.text_type, required=True) url = Field(six.text_type, required=True) is_public = Field(bool, required=True) created = Field(DateTime, required=True, help=':py:class:`~datetime.datetime` corresponding to ' 'creation date') updated = Field(DateTime, required=True, help=':py:class:`~datetime.datetime` corresponding to ' 'date last updated') links = ListField(Link) def update(self, **kwargs): """ Update this script. See :meth:`~lavaclient.api.scripts.Resource.update`. """ return self._client.scripts.update(self.id, **kwargs) def delete(self): """ Delete this script. See :meth:`~lavaclient.api.scripts.Resource.delete`. """ return self._client.scripts.delete(self.id)
class Component(Config): name = Field(six.text_type, required=True, validator=Length(min=1, max=255)) @classmethod def _describe(cls): return cls.describe().replace('\n', ', ')
class Config(FiggisConfig): general = Field(GeneralConfig) mpd = Field(MpdConfig) lastfm = Field(LastfmConfig) appearance = Field(AppearanceConfig) playlist = Field(PlaylistConfig) library = Field(LibraryConfig) scrobbles = Field(ScrobblesConfig) custom_orderers = Field(parse_custom_orders, default={}) def __init__(self, args=None, configuration=None): if configuration: super().__init__(configuration) return parser = ConfigParser() paths = CONFIG_PATHS if args and args.config: paths = [args.config] + CONFIG_PATHS # Make sure all sections are present parser.read([expand(path) for path in paths]) for section in self._fields: if not parser.has_section(section): parser.add_section(section) # Override from CLI data = self._override_config(parser, args) # Parse config without interpolation first, then do interpolation afterward. This prevents # fields with defaults from not being interpolated properly first_pass_conf = Config(configuration=data) first_pass = first_pass_conf.to_dict() # Do string interpolation for section, settings in first_pass.items(): nointerpolate = getattr(getattr(first_pass_conf, section), '__nointerpolate__', []) for key, value in settings.items(): settings[key] = value if key in nointerpolate else interpolate( value, settings) super().__init__(first_pass) def _override_config(self, parser, args): if args: if args.log: parser['general']['log'] = args.log return parser._sections
class Stack(Config, ReprMixin, BaseStack): table_columns = ('id', '_name', 'distro', '_description') table_header = ('ID', 'Name', 'Distro', 'Description') id = Field(six.text_type, required=True) name = Field(six.text_type, required=True) description = Field(six.text_type) links = ListField(Link) distro = Field(six.text_type, required=True, help='Distribution ID') services = ListField(StackService, required=True, help='See: :class:`StackService`') @property def _name(self): return '\n'.join(textwrap.wrap(self.name, 25)) @property def _description(self): return '\n'.join(textwrap.wrap(self.description, 30))
class Workload(Config, ReprMixin): table_columns = ('id', 'name', 'caption', '_description') table_header = ('ID', 'Name', 'Caption', 'Description') id = Field(six.text_type, required=True) name = Field(six.text_type, required=True) caption = Field(six.text_type, required=True) description = Field(six.text_type, required=True) @property def _description(self): return '\n'.join(textwrap.wrap(self.description, 30)) def recommendations(self, *args): """ recommendations(storage_size, persistence) Get recommendations for this workload. See :meth:`~lavaclient.api.workloads.Resource.recommendations`. """ return self._client.workloads.recommendations(self.id, *args)
class LastfmConfig(FiggisConfig): scrobble_days = Field(int, non_negative, default=180) user = Field(default='') api_key = Field(default='') api_secret = Field(default='') log_responses = Field(bool, default=False) url = Field(default='http://ws.audioscrobbler.com/2.0')
class AmbariCredential(Config, ReprMixin): table_columns = ('type', 'username') type = 'Ambari' username = Field(six.text_type, required=True) @property def id(self): """Equivalent to :attr:`username`""" return self.username def delete(self): """Delete s3 credential""" self.__client.credentials.delete_ambari(self.username)
class AbsoluteLimits(Config, ReprMixin): node_count = Field(AbsoluteLimit, required=True, help='See: :class:`AbsoluteLimit`') ram = Field(AbsoluteLimit, required=True, help='See: :class:`AbsoluteLimit`') disk = Field(AbsoluteLimit, required=True, help='See: :class:`AbsoluteLimit`') vcpus = Field(AbsoluteLimit, required=True, help='See: :class:`AbsoluteLimit`') def display(self): properties = [ ('Nodes', self.node_count.limit, self.node_count.remaining), ('RAM', self.ram.limit, self.ram.remaining), ('Disk', self.disk.limit, self.disk.remaining), ('VCPUs', self.vcpus.limit, self.vcpus.remaining), ] header = ('Property', 'Limit', 'Remaining') print_table(properties, header, title='Quotas')
class SSHKey(Config, ReprMixin): table_columns = ('type', 'name') table_header = ('Type', 'Name') type = 'SSH Key' name = Field(six.text_type, key='key_name', required=True) @property def id(self): """Equivalent to :attr:`name`""" return self.name def delete(self): """Delete this key""" self._client.credentials.delete_ssh_key(self.name)
class CloudFilesCredential(Config, ReprMixin): table_columns = ('type', 'username') table_header = ('Type', 'Username') type = 'Cloud Files' username = Field(six.text_type, required=True) @property def id(self): """Equivalent to :attr:`username`""" return self.username def delete(self): """Delete this credential""" self._client.credentials.delete_cloud_files(self.username)
class S3Credential(Config, ReprMixin): table_columns = ('type', 'access_key_id') table_header = ('Type', 'Access Key ID') type = 'Amazon S3' access_key_id = Field(six.text_type, required=True) @property def id(self): """Equivalent to :attr:`access_key_id`""" return self.access_key_id def delete(self): """Delete s3 credential""" self.__client.credentials.delete_s3(self.access_key_id)
class StackDetail(Stack, ReprMixin, BaseStack): __inherits__ = [Stack] table_columns = ('id', 'name', 'distro', 'created', '_description', '_services', '_node_group_ids') table_header = ('ID', 'Name', 'Distro', 'Created', 'Description', 'Services', 'Node Groups') created = Field(DateTime, required=True, help=':py:class:`~datetime.datetime` corresponding to ' 'creation date') node_groups = ListField(StackNodeGroup, required=True, help='See: :class:`StackNodeGroup`') def display(self): display_result(self, StackDetail, title='Stack') if self.node_groups: display_result(self.node_groups, StackNodeGroup, title='Node Groups') self._display_components() def _display_components(self): rows = [] for group in self.node_groups: group_column = chain([group.id], repeat('')) rows.extend( no_nulls((grp, comp['name'])) for grp, comp in six.moves.zip(group_column, group.components)) print_table(rows, ('Node Group', 'Name'), title='Components') @property def _description(self): return '\n'.join(textwrap.wrap(self.description, 60)) @property def _node_group_ids(self): return '\n'.join( textwrap.wrap(', '.join(group.id for group in self.node_groups)))
class Cluster(Config, ReprMixin, BaseCluster): """Basic cluster information""" table_columns = ('id', 'name', 'status', 'stack_id', 'created') table_header = ('ID', 'Name', 'Status', 'Stack', 'Created') id = Field(six.text_type, required=True) created = Field(DateTime, required=True, help=':py:class:`~datetime.datetime` corresponding to ' 'creation date') updated = Field(DateTime, required=True, help=':py:class:`~datetime.datetime` corresponding to ' 'date last updated') name = Field(six.text_type, required=True) status = Field(six.text_type, required=True) stack_id = Field(six.text_type, required=True) cbd_version = Field(int, required=True, help='API version at which cluster was created') links = ListField(Link)