Opened 5 years ago

Last modified 5 years ago

#6066 new enhancement

Use case for Cloud resources

Reported by: minondoa Owned by: minondoa
Priority: critical Milestone: AWS
Component: DRM4G Keywords:
Cc: carlos, minondoa, antonio

Description (last modified by antonio)

Este ticket le vamos ausar para discutir el caso de uso para recursos de
tipo cloud:

Para definir los recursos cloud, se tendría que hacer desde el mismo fichero "cloudsetup.json". La idea es usarlos para todas las clouds que se añadan al DRM4G, por eso se usa el JSON, que se puede leer como un diccionario, en lugar de tener que recorrer todos los elementos.

En cuanto a el resources.conf, tendría que parecerse a algo como esto:

[CESNET-METACLOUD]
enable         = true
communicator   = ssh
vm_communicator= op_ssh
private_key    = ~/.ssh/id_rsa
username       = user
vm_user        = drm4g_admin
frontend       = mar.meteo.unican.es
lrms           = pbs
vo= fedcloud   #esto todavia no existe y no estamos seguros de como llamarlo
cloud          = EGI FedCloud - CESNET-METACLOUD   #esto lo queremos cambiar el nombre a "site"
myproxy_server = myproxy1.egee.cesnet.cz
flavour        = Small
virtual_image  = Ubuntu-14.04
nodes          = 5
volume         = 0
max_jobs_running = 5
queue = short
max_jobs_in_queue=10

Change History (7)

comment:1 Changed 5 years ago by antonio

  • Description modified (diff)

comment:2 Changed 5 years ago by antonio

  • Cc antonio added
  • Reporter changed from antonio to minondoa

comment:3 in reply to: ↑ description Changed 5 years ago by antonio

Replying to minondoa:

Este ticket le vamos ausar para discutir el caso de uso para recursos de
tipo cloud:

Para definir los recursos cloud, se tendría que hacer desde el mismo fichero "cloudsetup.json". La idea es usarlos para todas las clouds que se añadan al DRM4G, por eso se usa el JSON, que se puede leer como un diccionario, en lugar de tener que recorrer todos los elementos.

Acuerdate de que ha de ser usable por el usuario y por eso usamos el formato MS-INI compuesto por secciones y claves-valores. Ponerlo en un JSON complica su edición por parte de un usuario.

En cuanto a el resources.conf, tendría que parecerse a algo como esto:

[CESNET-METACLOUD]
enable         = true
communicator   = ssh
vm_communicator= op_ssh
private_key    = ~/.ssh/id_rsa
username       = user
vm_user        = drm4g_admin
frontend       = mar.meteo.unican.es
lrms           = pbs
vo= fedcloud   #esto todavia no existe y no estamos seguros de como llamarlo
cloud          = EGI FedCloud - CESNET-METACLOUD   #esto lo queremos cambiar el nombre a "site"
myproxy_server = myproxy1.egee.cesnet.cz
flavour        = Small
virtual_image  = Ubuntu-14.04
nodes          = 5
volume         = 0
max_jobs_running = 5
queue = short
max_jobs_in_queue=10

Un recurso de tipo cloud, es muy especial, pero debería ser como uno de tipo HPC, no debería haber diferencia en su definición.

Imaginte que lo provisionas a mano. Esto es creas y configuras las instancias del cluster a mano. Estas instancias hacen uso de un gestor de colas (o no, pero este caso es especial).

$ crear cluster con 5 nodos y una cola (vamos a llamarala qbigmem) y un LRMS (i.e. PBS)

por tanto tenemos el cluster provisionado esta sería su configuración, como cualquier otro recurso:

[MiClusterEnAWS]
     communicator = ssh
         username = usuario
         frontend = ui.micluster.europe.aws.com
             lrms = pbs    
 max_jobs_running = 5
            queue = qbigmem
max_jobs_in_queue = 10

se podría especificar la public_key, pero vamos a dejarlo de forma sencilla, por el momento.

Planteame que problemas son los que ver a esta aproximación.

Recuerda que estamos construyendo desde lo que conocemos, y por tanto tenemos que ver que elementos son los que nos hacen falta.

Cuales son esos elementos?

comment:4 follow-up: Changed 5 years ago by minondoa

Acuerdate de que ha de ser usable por el usuario y por eso usamos el formato MS-INI compuesto por secciones y claves-valores. Ponerlo en un JSON complica su edición por parte de un usuario.

Puede que lo complique un poco, pero de esta manera se le puede dar una lista (de los sites/proveedores que funcionan) y puede tener guardado su configuración preferida.

También hace que sea mas sencillo cambiar de configuración el en "resources.conf", por ejemplo, si quisiese cambiar de tamaño de la próxima VM que vaya a crear, solo tendría que modificar el valor de la clave "cloud" (que ahora llamaremos "cloud_provider" , #6082). La otra opción sería escribir los valores directamente en el "resources.conf", pero tendría que buscarselos cada vez que quisiese hace un cambio. Creo que acabaría siendo mas costoso.

Un recurso de tipo cloud, es muy especial, pero debería ser como uno de tipo HPC, no debería haber diferencia en su definición.

Y si tu creas la VM por tu cuenta y solo quieres al DRM4G para mandarle jobs, no la hay. Pero nosotros estamos creandolas antes, para lo cual nos hace falta algo más de información.

comment:5 in reply to: ↑ 4 Changed 5 years ago by antonio

Estamos hablando de 2 problemas distintos. El primero y más importante es si separamos la parte de recursos de la parte de provision. Y el segundo que formato de fichero usamos (JSON, MS-INI).

Replying to minondoa:

Acuerdate de que ha de ser usable por el usuario y por eso usamos el formato MS-INI compuesto por secciones y claves-valores. Ponerlo en un JSON complica su edición por parte de un usuario.

Puede que lo complique un poco, pero de esta manera se le puede dar una lista (de los sites/proveedores que funcionan) y puede tener guardado su configuración preferida.

No lo entiendo, puedes desarrollarlo un poco más?

Creo que hay que trabajarse el libcloud, ya que esa separación ya la tienen hecha y usan el concepto de driver.

De todas formas el archivo de provisión se puede definir en MS-INI, que es mucho más fácil de editar. COmo digo son 2 problemas distintos.

También hace que sea mas sencillo cambiar de configuración el en "resources.conf", por ejemplo, si quisiese cambiar de tamaño de la próxima VM que vaya a crear, solo tendría que modificar el valor de la clave "cloud" (que ahora llamaremos "cloud_provider" , #6082). La otra opción sería escribir los valores directamente en el "resources.conf", pero tendría que buscarselos cada vez que quisiese hace un cambio. Creo que acabaría siendo mas costoso.

Hazme un ejemplo ... creo que es más fácil de entender que modelo tienes en la cabeza,

Un recurso de tipo cloud, es muy especial, pero debería ser como uno de tipo HPC, no debería haber diferencia en su definición.

Y si tu creas la VM por tu cuenta y solo quieres al DRM4G para mandarle jobs, no la hay. Pero nosotros estamos creandolas antes, para lo cual nos hace falta algo más de información.

Si yo creo la VM por mi cuenta solo tengo que ar el recurso de alta como un fork y un ssh como communicator ... como se hace hasta ahora.

Hay algo que no estoy viendo?

comment:6 Changed 5 years ago by carlos

Creo que la idea de usar libcloud es la mejor. Tanto por su facilidad como por su potencia. Yo propongo desarrollar un conjunto de cloud_providers como está desarrollados los managers o los communicators con una clase base en el fichero init.py . Yo propongo esta como partida:

import os.path
import time
from math           import ceil

class Instance(object):

    DEFAULT_USER = "drm4gadm"
    SECURITY_GROUP_NAME = "drm4g_group"
    TIMEOUT = 600 # seconds
    WAIT_PERIOD = 3 # seconds
    instance_pricing = 0.0
    start_time = 0.0

    def __init__(self, basic_data=None):
        pass

    def create(self):
        pass

    def destroy(self):
        pass

    def get_public_ip(self):
        pass

    def get_private_ip(self):
        pass

    def create_security_group(self):
        pass

    def wait_until_running(self):
        pass

    def _start_time(self):
        self.start_time = time.time()

    def running_time(self):
        if not self.start_time:
            return 0
        else:
            return (time.time() - self.start_time)/3600

    def current_balance(self):
        running_hours = ceil(self.running_time())
        return running_hours * self.instance_pricing

    def generate_cloud_config(self, public_key, user=None, user_cloud_config=None):
        """
        Generate the cloud-config file to be used in the user_data
        """
        if not user:
            user = self.DEFAULT_USER
        config = """#cloud-config
users:
  - name: %s
    sudo: ALL=(ALL) NOPASSWD:ALL
    lock-passwd: true
    ssh-import-id: %s
    ssh-authorized-keys:
      - %s
""" % (user, user, public_key)
        if user_cloud_config:
            config += "\n%s\n\n" % user_cloud_config.replace("\\n", "\n")
        return config

Y a modo de ejemplo he implementado su versión para EC2 con libcloud:

import threading
from libcloud.compute.types      import Provider
from libcloud.compute.providers  import get_driver
from libcloud.compute.types      import NodeState

class EC2(Instance):

    def __init__(self, basic_data):
        self.node = None
        self._lock = threading.Lock()
        try :
            access_id = basic_data[ 'access_id' ]
            secret_key = basic_data[ 'secret_key' ]
        except :
            raise Exception( "No correct auth data has been specified."
                             "Please review your 'access_id' and 'secret_key'" )

        region = basic_data.get('region', 'eu-west-1')
        cls = get_driver(Provider.EC2)
        try:
            self.driver = cls( access_id, secret_key, region = region )
        except:
            raise Exception( "No correct region has been specified."
                             "Please review your 'region'" )

        size_id = basic_data.get('size', 't2.micro')
        try:
            self.size = [s for s in self.driver.list_sizes() if s.id == size_id ][0]
        except:
            raise Exception( "No correct size_id has been specified."
                             "Please review your 'size_id'" )
        self.instance_pricing = float(basic_data.get('pricing', self.size.price))

        image_id = basic_data.get('image', 'ami-c51e3eb6')
        try:
            self.image = self.driver.get_image( image_id )
        except:
            raise Exception( "No correct image_id has been specified."
                             "Please review your 'image_id'" )

        # Note: This key will be added to the authorized keys for the user admin
        try:
            public_key_path = os.path.expanduser(basic_data.get('public_key', '~/.ssh/id_rsa.pub'))
        except :
            raise Exception( "No correct public_key has been specified."
                             "Please review your 'public_key'" )
        with open(public_key_path) as fp:
            public_key = fp.read().strip()
        self.deployment = self.generate_cloud_config(public_key, user=basic_data.get('cloud_user', self.DEFAULT_USER),
                                                     user_cloud_config=basic_data.get('cloud_config_script'))

    def create_security_group(self):
        if not self.SECURITY_GROUP_NAME in self.driver.ex_list_security_groups():
            self.driver.ex_create_security_group(name=self.SECURITY_GROUP_NAME, description="DRM4G group open ssh port")
            self.driver.ex_authorize_security_group(name=self.SECURITY_GROUP_NAME, protocol='tcp', from_port=22,
                                                    to_port=22, cidr_ip='0.0.0.0/0')

    def create(self):
        with self._lock:
            self.create_security_group()
        self.node = self.driver.create_node(name='drm4g_VM', image=self.image, size=self.size,
                    ex_security_groups=[self.SECURITY_GROUP_NAME], ex_userdata=self.deployment)
        self._start_time()

    def wait_until_running(self):
        start = time.time()
        end = start + self.TIMEOUT

        while time.time() < end:
            node = self.driver.list_nodes([self.node.id])[0]
            if node.state == NodeState.RUNNING:
                return node.public_ips[0]
            else:
                time.sleep(self.WAIT_PERIOD)
                continue

        raise Exception("Timed out after %s seconds" % (self.TIMEOUT))

    def destroy(self):
        self.driver.destroy_node(self.node)

    def get_public_ip(self):
        if self.node:
            node = self.driver.list_nodes([self.node.id])[0]
            if node.state == NodeState.RUNNING:
                return node.public_ips[0]
            else:
                return None
        else:
            return None

    def get_private_ip(self):
        if self.node:
            return self.node.private_ips[0]
        else:
            return None

Last edited 5 years ago by carlos (previous) (diff)

comment:7 Changed 5 years ago by minondoa

New configuration keys:

For EC2:

[amazon_vm]
#By default
vm_user            = drm4g_adm
vm_communicator    = ssh
lrms               = fork
private_key        = ~/.ssh/id_rsa
public_key         = private_key + ".pub"
vm_config          = $DRM4G_DIR/.drm4g/etc/cloud_config.conf
node_safe_time     = 5
volume             = 0
volume_type        = gp2
node_min_pool_size = 0
node_max_pool_size = 10
#the following values correspond to amazon's temporary free tier 
region             = eu-west-1
size               = t2.micro
image              = ami-c51e3eb6
pricing            = 0.0
soft_billing       = 0.0
hard_billing       = 0.0

#Obligatory
enable             = true
cloud_connector    = ec2
access_id          = XXXXXXXXXXXXXXXX
secret_key         = XXXXXXXXXXXXXXXX
max_jobs_running   = 1

For ROCCI:

[fedcloud_vm]
#By default
communicator       = local
vm_user            = drm4g_adm
vm_communicator    = ssh
lrms               = fork
private_key        = ~/.ssh/id_rsa
public_key         = private_key + ".pub"
myproxy_server     = myproxy1.egee.cesnet.cz
scratch            = ~/.drm4g/jobs #only when not using the local communicator
vm_config          = $DRM4G_DIR/.drm4g/etc/cloud_config.conf
node_safe_time     = 5
volume             = 0
node_min_pool_size = 0
node_max_pool_size = 10
region             = EGI FedCloud - CESNET-METACLOUD
size               = Small
image              = Ubuntu-14.04

#Obligatory
enable             = true
cloud_connector    = rocci
max_jobs_running   = 1
#in case that the user doesn't use 'local' as the communicator, the following values will be needed
username           = <remote_user>
frontend           = <remote_address>
Last edited 5 years ago by minondoa (previous) (diff)
Note: See TracTickets for help on using tickets.