from __future__ import print_function
from cloudmesh_client import CloudmeshDatabase
from cloudmesh_client.common.Printer import Printer
from cloudmesh_client.common.ConfigDict import ConfigDict
from cloudmesh_client.exc import NoActiveClusterException
from cloudmesh_client.shell.console import Console
# noinspection PyPep8Naming
[docs]class readable_classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
[docs]class Names:
VM_COUNTER = 'vm_counter'
CLUSTER_COUNTER = 'cluster'
STACK_COUNTER = 'stack'
ACTIVE_STACK = 'active-stack'
ACTIVE_CLUSTER = 'active-cluster'
ACTIVE_SPECIFICATION = 'active-specification'
# noinspection PyBroadException
[docs]class Default(object):
"""
Cloudmesh contains the concept of defaults. Defaults can have
categories (we will rename cloud to categories). A category can be a
cloud name or the name 'general'. The category general is a 'global'
name space and contains defaults of global value (in future we will
rename the value to global).
"""
__kind__ = "default"
__provider__ = "general"
cm = CloudmeshDatabase()
[docs] @classmethod
def list(cls,
category=None,
order=None,
header=None,
output=None):
"""
lists the default values in the specified format.
TODO: This method has a bug as it uses format and output,
only one should be used.
:param category: the category of the default value. If general is used
it is a special category that is used for global values.
:param format: json, table, yaml, dict, csv
:param order: The order in which the attributes are returned
:param output: The output format.
:return:
"""
if order is None:
order, header = None, None
# order = ['user',
# 'category',
# 'name',
# 'value',
# 'updated_at']
# order, header = Attributes(cls.__kind__, provider=cls.__provider__)
if output is None:
output = Default.output or 'table'
try:
if category is None:
result = cls.cm.all(kind=cls.__kind__)
else:
result = cls.cm.all(provider=category, kind=cls.__kind__)
table = Printer.write(result,
output=output)
return table
except Exception as e:
Console.error("Error creating list", traceflag=False)
Console.error(e.message)
return None
#
# GENERAL SETTER AND GETTER METHOD
#
[docs] @classmethod
def set(cls, key, value, category='general', user=None, type='str'):
"""
sets the default value for a given category
:param key: the dictionary key of the value to store it at.
:param value: the value
:param user: the username to store this default value at.
:return:
"""
try:
o = cls.get(name=key, category=category)
if o is not None:
cls.cm.update(kind=cls.__kind__,
provider=cls.__provider__,
filter={'name': key},
update={'value': value,
'type': type,
'user': user,
'category': category})
else:
t = cls.cm.table(provider=cls.__provider__, kind=cls.__kind__)
o = t(name=key, value=value, type=type, user=user, category=category)
cls.cm.add(o)
cls.cm.save()
except Exception as e:
Console.error("problem setting key value {}={}".format(key, value), traceflag=False)
[docs] @classmethod
def get(cls, name=None, category='general'):
o = cls.cm.find(provider="general",
kind="default",
scope="first",
category=category,
name=name,
output="dict")
if o is None:
return None
if o.type == 'int':
return int(o.value)
elif o.type == 'bool':
return o.value
elif str(o.value) in ["True", "False"]:
result = o.value == "True"
return result
else:
return (o.value)
[docs] @classmethod
def delete(cls, name, category=None):
if category is None:
result = cls.cm.delete(name=name, provider=cls.__provider__, kind=cls.__kind__)
else:
result = cls.cm.delete(name=name, provider=cls.__provider__, kind=cls.__kind__, category=category)
return result
[docs] @classmethod
def clear(cls):
"""
deletes all default values in the database.
:return:
"""
cls.cm.delete(provider=cls.__provider__, kind=cls.__kind__)
@readable_classproperty
def interactive(cls):
return cls.get(name="interactive")
@readable_classproperty
def index(cls):
return cls.get(name="index")
@readable_classproperty
def cloud(cls):
return cls.get(name="cloud")
@readable_classproperty
def image(cls):
return cls.get(name="image", category=cls.cloud)
@readable_classproperty
def flavor(cls):
return cls.get(name="flavor", category=cls.cloud)
@readable_classproperty
def vm(cls):
return cls.get(name="vm")
@readable_classproperty
def group(cls):
return cls.get(name="group")
@readable_classproperty
def secgroup(cls):
return cls.get(name="secgroup")
@readable_classproperty
def key(cls):
return cls.get(name="key")
@readable_classproperty
def refresh(cls):
return cls.get(name="refresh")
@readable_classproperty
def debug(cls):
value = cls.get(name="debug")
if value is None:
cls.set_debug(True)
return True
return value
@readable_classproperty
def active_stack(cls):
return cls.get(name=Names.ACTIVE_STACK)
@readable_classproperty
def cluster(cls):
return cls.get(name=Names.ACTIVE_CLUSTER)
@readable_classproperty
def cluster_counter(cls):
return cls.get(name=Names.CLUSTER_COUNTER)
@readable_classproperty
def active_cluster(cls):
name = cls.cluster
if not name:
raise NoActiveClusterException()
# this import is here rather than the top to avoid circular
# dependencies
from cloudmesh_client.platform.virtual_cluster.cluster import Cluster
return Cluster.from_name(name)
@readable_classproperty
def active_specification(cls):
return cls.get(name=Names.ACTIVE_SPECIFICATION)
@readable_classproperty
def user(cls):
return cls.get(name="user")
@readable_classproperty
def timer(cls):
return cls.get(name="timer")
@readable_classproperty
def loglevel(cls):
return cls.get(name="loglevel")
@readable_classproperty
def output(cls):
return cls.get(name="output")
[docs] @classmethod
def set_loglevel(cls, level):
level = level or 'debug'
level = level.lower()
if level in ['debug',
'info',
'warnin',
'error',
'critical']:
cls.set("loglevel", level)
else:
Console.error("unkown logging level. Setting to debug.", traceflag=False)
cls.set("loglevel", 'debug')
# ###################################
# COUNTER
# ###################################
[docs] @classmethod
def generate_name(cls, counter_name, display_name=None,
prefix=None, fill=3):
"""Generate a name based on a counter
:param str counter_name: name of the counter in the database
:param str display_name: pretty name to show user (defaults to ``counter_name``)
:param str prefix: prefix the generated name with this
:param int fill: number of zeros to fill with
:returns: a generated name
:rtype: :class:`str`
"""
prefix = (prefix + '-') if prefix else ''
counter = cls.get_counter(name=counter_name)
index = str(counter).zfill(fill)
name = prefix + (display_name or counter_name) + '-' + index
cls.incr_counter(name=counter_name)
return name
[docs] @classmethod
def incr_counter(cls, name="index"):
count = cls.get_counter(name=name)
count += 1
cls.set_counter(name=name, value=count)
[docs] @classmethod
def get_counter(cls, name="index"):
"""
Function that returns the prefix username and count for vm naming.
If it is not present in db, it creates a new entry.
:return:
"""
count = cls.get(name=name)
if count is None:
count = 1
cls.set_counter(name, count)
return int(count)
[docs] @classmethod
def set_counter(cls, name, value):
"""
Special function to update vm prefix count.
:param name:
:param value:
:param user:
:return:
"""
cls.set(name, str(value), type='int')
cls.set(name, str(value), type='int')
[docs] @classmethod
def set_output(cls, value):
cls.output("output", value)
[docs] @classmethod
def set_user(cls, value):
"""
sets the cloud in the category general
:param value: the cloud as defined in cloudmesh.yaml
:return:
"""
cls.set("user", value)
@readable_classproperty
def purge(cls):
return cls.get(name="purge")
[docs] @classmethod
def set_cloud(cls, value):
"""
sets the cloud in the category general
:param value: the cloud as defined in cloudmesh.yaml
:return:
"""
cls.set("cloud", value)
[docs] @classmethod
def set_vm(cls, value):
"""
sets the cloud in the category general
:param value: the cloud as defined in cloudmesh.yaml
:return:
"""
cls.set("vm", value)
[docs] @classmethod
def set_image(cls, value, category):
"""
sets the default image for a specific category.
:param value: the image uuid or name
:param category: the category
:return:
"""
cls.set("image", value, category=category)
[docs] @classmethod
def get_image(cls, category=None):
"""
returns the image for a particular category
:param category: the category
:return:
"""
if category is None:
category = cls.cloud
return cls.get(name="image", category=category)
[docs] @classmethod
def set_flavor(cls, value, category):
"""
sets the default flavor for a particular category
:param value: the flavor name or uuid
:param category: the category
:return:
"""
cls.set("flavor", value, category=category)
[docs] @classmethod
def get_flavor(cls, category=None):
"""
gets ths flavor default for a category
:param category: the category
:return:
"""
if category is None:
category = cls.cloud
return cls.get(name="flavor", category=category)
[docs] @classmethod
def set_group(cls, value):
"""
sets the default group
:param value: the group name
:return:
"""
cls.set("group", value)
[docs] @classmethod
def set_secgroup(cls, value):
"""
sets the default group
:param value: the group name
:return:
"""
cls.set("secgroup", value)
[docs] @classmethod
def set_key(cls, name):
"""
:param name: the key name
:return:
"""
cls.set("key", name)
[docs] @classmethod
def set_cluster_counter(cls, value):
"""
sets the default cluster
:param value: the cluster name as defined in the cloudmesh yaml file.
:return:
"""
cls.set(Names.CLUSTER_COUNTER, value)
[docs] @classmethod
def set_specification(cls, value):
"""Sets the default specification
:param cls:
:param value:
:returns:
:rtype:
"""
cls.set(Names.ACTIVE_SPECIFICATION, value)
[docs] @classmethod
def set_debug(cls, value):
"""
enables debugging
:param value: True/False
:return:
"""
Console.set_debug(value)
if str(value) in ["on", "True"]:
cls.set("debug", "True")
else:
cls.set("debug", "False")
[docs] @classmethod
def set_refresh(cls, value):
"""
sets the default for all clouds to refresh
:param value:
:return:
"""
if str(value) in ["on", "True"]:
cls.set("refresh", "True")
else:
cls.set("refresh", "False")
[docs] @classmethod
def set_purge(cls, value):
"""
sets the default for all clouds to refresh
:param value:
:return:
"""
if str(value) in ["on", "True"]:
cls.set("purge", "True")
else:
cls.set("purge", "False")
[docs] @classmethod
def set_interactive(cls, value):
"""
sets the default for all clouds to refresh
:param value:
:return:
"""
if str(value) in ["on", "True"]:
cls.set("interactive", "True")
else:
cls.set("interactive", "False")
[docs] @classmethod
def set_timer(cls, value):
"""
sets the default for all clouds to timer
:param value:
:return:
"""
cls.set("timer", value)
[docs] @classmethod
def set_stack(cls, name):
"""Sets the stack name
:param str name: stack name
"""
cls.set(Names.ACTIVE_STACK, name)
[docs] @classmethod
def set_cluster(cls, value):
"""Sets the cluster
:param str value: cluster name
"""
cls.set(Names.ACTIVE_CLUSTER, value)
[docs] @classmethod
def load(cls, filename):
config = ConfigDict(filename=filename)["cloudmesh"]
clouds = config["clouds"]
# FINDING DEFAULTS FOR CLOUDS
for cloud in clouds:
db = {
"image": cls.get(name="image", category=cloud),
"flavor": cls.get(name="flavor", category=cloud),
}
defaults = clouds[cloud]["default"]
for attribute in ["image", "flavor"]:
value = db[attribute]
if attribute in defaults:
value = db[attribute] or defaults[attribute]
empty = cls.get(name=attribute,category=cloud)
if empty is None:
cls.set(attribute, value, category=cloud)
# FINDING DEFAUlTS FOR KEYS
# keys:
# default: id_rsa
# keylist:
# id_rsa: ~/.ssh/id_rsa.pub
# key_db = SSHKeyDBManager()
name_key = cls.key
#
# SET DEFAULT KEYS
#
if "keys" in config:
keys = config["keys"]
name = keys["default"]
if name in keys["keylist"]:
value = name_key or keys["keylist"][name]
# key_db.add(value, keyname=name)
# Check if the key is already set
exist_key = cls.key
# Set the key only if there is no existing value in the DB.
if exist_key is None:
cls.set_key(name)
else:
if cls.key is not None and cls.user is not None:
pass
elif cls.key is None and cls.user is not None:
cls.key = cls.user
else:
Console.error("Please define a key first, e.g.: cm key add --ssh <keyname>", traceflag=False)