from __future__ import print_function
from cloudmesh_client.common.ConfigDict import ConfigDict
from cloudmesh_client.common.todo import TODO
# add imports for other cloud providers in future
from cloudmesh_client.shell.console import Console
from cloudmesh_client.cloud.ListResource import ListResource
from cloudmesh_client.common.Printer import Printer
from cloudmesh_client.db import CloudmeshDatabase
from cloudmesh_client.cloud.iaas.CloudProvider import CloudProvider
from cloudmesh_client.common.Error import Error
from uuid import UUID
from cloudmesh_client.common.dotdict import dotdict
from builtins import input
from pprint import pprint
from cloudmesh_client.cloud.network import Network
from cloudmesh_client.default import (Names, Default)
import traceback
# noinspection PyPep8Naming
[docs]class Vm(ListResource):
cm = CloudmeshDatabase()
[docs] @classmethod
def generate_vm_name(cls, prefix=None, fill=3):
return Default.generate_name(Names.VM_COUNTER,
display_name=Default.user,
prefix=prefix,
fill=fill)
[docs] @classmethod
def uuid(cls, name, category=None):
vm = cls.get(name, category=category)
if vm is None:
return None
return vm.uuid
[docs] @classmethod
def get(cls, key, category=None):
"""
returns the value of the first objects matching the key
with the given category.
:param key: The dictionary key
:param category: The category
:return:
"""
if category is None:
o = cls.cm.find(kind='vm',
output='dict',
scope='first',
name=key)
else:
o = cls.cm.find(category=category,
kind='vm',
output='dict',
scope='first',
name=key)
return o
[docs] @classmethod
def construct_ip_dict(cls, ip_addr, name=None):
# TODO kilo cloud as defualt should be avoided
if name is None:
Console.error("cloud name not set")
return None
try:
d = ConfigDict("cloudmesh.yaml")
cloud_details = d["cloudmesh"]["clouds"][name]
# Handle Openstack Specific Output
if cloud_details["cm_type"] == "openstack":
ipaddr = {}
for network in ip_addr:
index = 0
for ip in ip_addr[network]:
ipaddr[index] = {}
ipaddr[index]["network"] = network
ipaddr[index]["version"] = ip["version"]
ipaddr[index]["addr"] = ip["addr"]
index += 1
return ipaddr
# Handle EC2 Specific Output
if cloud_details["cm_type"] == "ec2":
# Console.TODO("ec2 ip dict yet to be implemented")
# TODO.implement()
# :type str: ip_addr
index = 0
ipaddr = {}
ipaddr[index] = {}
ipaddr[index]['addr'] = ip_addr
return ipaddr
# Handle Azure Specific Output
if cloud_details["cm_type"] == "azure":
index = 0
ipaddr = {}
for ip in ip_addr:
ipaddr[index] = {}
ipaddr[index]["network"] = ip
ipaddr[index]["version"] = 'ipv4'
ipaddr[index]["addr"] = ip
index += 1
return ipaddr
except Exception as e:
Console.error("error in vm construct dict %s" % e, traceflag=True)
[docs] @classmethod
def isUuid(cls, name):
try:
UUID(name, version=4)
return True
except ValueError:
return False
[docs] @classmethod
def boot(cls, **kwargs):
arg = dotdict(kwargs)
for a in ["key", "name", "image", "flavor"]:
if a not in kwargs:
raise ValueError(a + " not in arguments to vm boot")
conf = ConfigDict("cloudmesh.yaml")
arg.user = arg.user or conf["cloudmesh"]["profile"]["user"]
arg.group = arg.group or Default.group
cloud_provider = CloudProvider(arg.cloud).provider
if "nics" in arg:
nics = arg.nics
else:
nics = None
basic_dict = {
"cloud": arg.cloud,
"name": arg.name,
"image": arg.image,
"flavor": arg.flavor,
"key": arg.key,
"secgroup": [arg.secgroup],
"nics": nics,
"meta": {
'kind': 'cloudmesh',
'group': arg.group,
'cluster': arg.get('cluster', None),
'image': arg.image,
'flavor': arg.flavor,
'key': arg.key,
'category': arg.cloud
}
}
# Special case for Azure where certificate details needs to be added
if arg.cloud == "azure":
kwargs = dict()
kwargs['kind'] = "key_azure"
db_result = cls.cm.find(**kwargs)
# pprint("Key DB results")
key_result = None
try:
for key in db_result:
if key['name'] == arg.key:
pprint("Found the key")
key_result = key
break
if key_result is not None:
new_dict_items = dict()
new_dict_items['cert_thumbprint'] = key_result['fingerprint']
new_dict_items['pub_key_path'] = key_result['key_path']
new_dict_items['cert_path'] = key_result['certificate']
new_dict_items['pfx_path'] = key_result['pfx_path']
basic_dict.update(new_dict_items)
else:
pprint("None found in DB")
except:
traceback.print_exc()
pprint("Exception while processing azure boot arguments")
d = dotdict(basic_dict)
Console.ok("Machine {name} is being booted on cloud {cloud} ..."
.format(**arg))
print(Printer.attribute(d))
vm = cloud_provider.boot_vm(**d)
if vm is not None:
cls.refresh(cloud=arg.cloud)
try:
# TODO: Repair db schema for vm_azure, vm_libcloud,
# vm_openstack. The following set only works with
# openstack, no libcloud, no azure
cls.cm.set(d.name, "key", d.key, scope="first", kind="vm")
cls.cm.set(d.name, "image", d.image, scope="first", kind="vm")
cls.cm.set(d.name, "flavor", d.flavor, scope="first", kind="vm")
cls.cm.set(d.name, "group", arg.group, scope="first", kind="vm")
cls.cm.set(d.name, "user", arg.user, scope="first", kind="vm")
cls.cm.set(d.name, 'username', arg.username, scope='first', kind='vm')
cls.cm.set(d.name, 'cluster', arg.cluster, scope='first', kind='vm')
except:
# cm.set error is identified as a warning, not an error
import sys
Console.warning("cls.cm.set error: %s" % (sys.exc_info()[0]))
# update group and key
#
# cls.cm.update("vm", name=data.name)
return vm
[docs] @classmethod
def start(cls, **kwargs):
arg = dotdict(kwargs)
cloud_provider = CloudProvider(arg.cloud).provider
for server in kwargs["servers"]:
cloud_provider.start_vm(server)
Console.ok("Machine {:} is being started on {:} Cloud..."
.format(server, cloud_provider.cloud))
# Explicit refresh called after VM start, to update db.
# cls.refresh(cloud=kwargs["cloud"])
[docs] @classmethod
def stop(cls, **kwargs):
arg = dotdict(kwargs)
cloud_provider = CloudProvider(arg.cloud).provider
for server in kwargs["servers"]:
cloud_provider.stop_vm(server)
Console.ok("Machine {:} is being stopped on {:} Cloud..."
.format(server, cloud_provider.cloud))
# Explicit refresh called after VM stop, to update db.
# cls.refresh(cloud=kwargs["cloud"])
[docs] @classmethod
def delete(cls, **kwargs):
arg = dotdict(kwargs)
force = kwargs.get("force", Default.purge)
if "cloud" in arg:
cloud_provider = CloudProvider(arg.cloud).provider
for server in kwargs["servers"]:
vm = cls.cm.find(name=server, kind="vm",
cloud=arg.cloud, scope="first")
#vm_by_id = cls.cm.find(cm_id=server, kind="vm", cloud=arg.cloud, scope="first")
#print (vm)
#print(vm_by_id)
#vm = vm or vm_by_id
if vm:
provider = vm["provider"]
cloud = vm["category"]
# If server has a floating ip associated, release it
server_dict = Network.get_instance_dict(
cloudname=arg.cloud,
instance_id=server)
floating_ip = server_dict["floating_ip"]
if floating_ip is not None:
Network.disassociate_floating_ip(
cloudname=arg.cloud,
instance_name=server,
floating_ip=floating_ip)
cloud_provider.delete_vm(server)
if force:
cls.cm.delete(kind="vm",
provider=provider,
category=cloud,
name=server) # delete the record from db
Console.ok(
"VM record {:} is being deleted from the local database..."
.format(server))
else:
cls.cm.set(server, "status", "deleted",
kind="vm", scope="first")
# Console.ok("VM {:} is being deleted on {:} cloud...".format(server, cloud_provider.cloud))
else:
Console.error("VM {:} can not be found."
.format(server), traceflag=False)
else:
clouds = set()
for server in arg.servers:
vm = cls.cm.find(kind="vm", name=server, scope="first")
if vm:
cloud = vm["category"]
provider = vm["provider"]
cloud_provider = CloudProvider(cloud).provider
clouds.add(cloud)
cloud_provider.delete_vm(server)
if force:
cls.cm.delete(kind="vm",
provider=provider,
category=cloud,
name=server)
Console.ok(
"VM record {:} is being deleted from the local database..."
.format(server))
else:
cls.cm.set(server, "status", "deleted",
kind="vm", scope="first")
# Console.ok("VM {:} is being deleted on {:} cloud...".format(server, cloud))
else:
Console.error("VM {:} can not be found."
.format(server), traceflag=False)
[docs] @classmethod
def get_vms_by_name(cls, name, cloud):
vm_data = cls.cm.find(kind="vm", name=name, category=cloud)
if vm_data is None or len(vm_data) == 0:
raise RuntimeError("VM data not found in database.")
return vm_data
[docs] @classmethod
def get_vms_by_group(cls, name):
group = cls.cm.find(kind="group", name=name)
return group
[docs] @classmethod
def get_vm(cls, name):
vm = cls.cm.find(kind="vm", name=name)
return vm
[docs] @classmethod
def rename(cls, **kwargs):
arg = dotdict(kwargs)
cloud_provider = CloudProvider(kwargs["cloud"]).provider
# Check for vms with duplicate names in DB.
vms = cls.get_vms_by_name(name=arg.oldname, cloud=arg.cloud)
if len(vms) > 1:
users_choice = "y"
if not arg.force:
print("More than 1 vms found with the same name as {}."
.format(server))
users_choice = input(
"Would you like to auto-order the new names? (y/n): ")
if users_choice.strip() == "y":
count = 1
for index in vms:
count_new_name = "{0}{1}".format(arg.newname, count)
# print(vms[index])
cloud_provider.rename_vm(vms[index]["uuid"],
count_new_name)
print(
"Machine {0} with UUID {1} renamed to {2} on {3} cloud"
.format(vms[index]["name"],
vms[index]["uuid"],
count_new_name,
cloud_provider.cloud))
count += 1
elif users_choice.strip() == "n":
cloud_provider.rename_vm(arg.oldname, arg.newname)
print(
"Machine {0} renamed to {1} on {2} Cloud..."
.format(arg.oldname, arg.newname, cloud_provider.cloud))
else:
Console.error("Invalid Choice.")
return
else:
cloud_provider.rename_vm(arg.oldname, arg.newname)
print("Machine {0} renamed to {1} on {2} Cloud..."
.format(arg.oldname, arg.newname, cloud_provider.cloud))
# Explicit refresh called after VM rename, to update db.
cls.refresh(cloud=arg.cloud)
[docs] @classmethod
def info(cls, **kwargs):
raise NotImplementedError()
[docs] @classmethod
def list(cls, **kwargs):
"""
This method lists all VMs of the cloud
"""
arg = dotdict(kwargs)
if "name" in arg:
arg.name = arg.name
arg.output = arg.output or 'table'
# pprint (kwargs)
# prevent circular dependency
def vm_groups(vm):
"""
:param vm: name of the vm
:return: a list of groups the vm is in
"""
try:
query = {
'kind': "group",
'provider': 'general',
"species": "vm",
"member": vm,
"scope": 'all',
"output": 'dict'
}
d = cls.cm.find(**query)
groups_vm = set()
if d is not None and len(d) > 0:
for vm in d:
groups_vm.add(vm['name'])
return list(groups_vm)
except Exception as ex:
Console.error(ex.message)
return []
try:
if "name" in arg and arg.name is not None:
if cls.isUuid(arg.name):
elements = cls.cm.find(kind="vm",
category=arg.category,
uuid=arg.name)
else:
elements = cls.cm.find(kind="vm",
category=arg.category,
label=arg.name)
else:
elements = cls.cm.find(kind="vm",
category=arg.category)
if elements is None or len(elements) == 0:
return None
for elem in elements:
element = elem
name = element["name"]
groups = vm_groups(name)
element["group"] = ','.join(groups)
# print(elements)
# order = ['id', 'uuid', 'name', 'cloud']
(order, header) = CloudProvider(arg.category).get_attributes("vm")
# order = None
if "name" in arg and arg.name is not None:
return Printer.attribute(elements[0],
output=arg.output)
else:
return Printer.write(elements,
order=order,
output=arg.output)
except Exception as ex:
Console.error(ex.message)
[docs] @classmethod
def clear(cls, **kwargs):
raise NotImplementedError()
[docs] @classmethod
def refresh(cls, **kwargs):
# print("Inside refresh")
refreshed = cls.cm.refresh("vm", kwargs["cloud"])
# update counter
vms = cls.cm.find(kind='vm')
me = Default.user
for vm in vms:
name = vm['name']
if not name.startswith(me):
continue
number = name.split('-')[-1]
try:
# +1 as the stored counter is the next available counter
new_counter = int(number) + 1
except ValueError:
# name is not formatted correctly, possibly due to not
# being started using cloudmesh
continue
old_counter = Default.get_counter(Names.VM_COUNTER)
counter = max(new_counter, old_counter)
Default.set_counter(Names.VM_COUNTER, counter)
Console.debug_msg('Set counter ' + Names.VM_COUNTER + ' to ' +
str(Default.get_counter(Names.VM_COUNTER)))
return refreshed
[docs] @classmethod
def status_from_cloud(cls, **kwargs):
cloud_provider = CloudProvider(kwargs["cloud"]).provider
vm = cloud_provider.get_vm(name=kwargs["name"])
return vm["status"]
[docs] @classmethod
def set_login_user(cls, name=None, cloud=None, username=None):
# cls.cm.set(name, "username", username, kind="vm", scope="first")
vm = Vm.get(name, category=cloud)
if vm is None:
Console.error("VM could not be found", traceflag=False)
return
else:
cls.cm.update(kind="vm",
provider=vm["provider"],
filter={'name': name},
update={"username": username}
)
[docs] @classmethod
def get_login_user(cls, name, cloud):
print(name, cloud)
Console.error("this method is wrong implemented")
'''
if cls.isUuid(name):
uuid = name
else:
vm_data = cls.cm.find(kind="vm", category=cloud, label=name)
if vm_data is None or len(vm_data) == 0:
raise RuntimeError("VM with label {} not found in database.".format(name))
uuid = list(vm_data.values())[0]["uuid"]
# print(uuid)
user_map_entry = cls.cm.find("VMUSERMAP", vm_uuid=uuid)
# print(user_map_entry)
if user_map_entry is None or len(user_map_entry) == 0:
return None
else:
return list(user_map_entry.values())[0]["username"]
'''
[docs] @classmethod
def get_vm_public_ip(cls, vm_name, cloud):
"""
:param vm_name: Name of the VM instance whose Public IP has to be retrieved from the DB
:param cloud: Libcloud supported Cloud provider name
:return: Public IP as a list
"""
public_ip_list = []
vms = cls.get_vms_by_name(vm_name, cloud)
keys = vms.keys()
if keys is not None and len(keys) > 0:
public_ip = vms[keys[0]]["public_ips"]
if public_ip is not None and public_ip != "":
public_ip_list.append(public_ip)
return public_ip_list