From 40e1bcedb62477b20e458fb0d329c915d792863b Mon Sep 17 00:00:00 2001 From: tierno Date: Wed, 9 Aug 2017 09:12:04 +0200 Subject: [PATCH] Allow combining user-data and ssh-keys with cloud-init Change-Id: I33199e9a1877c12c09e69d1667bb2cb211ce16d1 Signed-off-by: tierno --- osm_ro/nfvo.py | 35 ++++++++++++++--------- osm_ro/vimconn.py | 3 +- osm_ro/vimconn_openstack.py | 55 +++++++++++++++++++++++++++++++++---- osm_ro/vimconn_vmware.py | 3 +- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/osm_ro/nfvo.py b/osm_ro/nfvo.py index f93e5514..fa87205f 100644 --- a/osm_ro/nfvo.py +++ b/osm_ro/nfvo.py @@ -395,12 +395,12 @@ def check_vnf_descriptor(vnf_descriptor, vnf_descriptor_version=1): name_dict[ interface["name"] ] = "overlay" vnfc_interfaces[ vnfc["name"] ] = name_dict # check bood-data info - if "boot-data" in vnfc: - # check that user-data is incompatible with users and config-files - if (vnfc["boot-data"].get("users") or vnfc["boot-data"].get("config-files")) and vnfc["boot-data"].get("user-data"): - raise NfvoException( - "Error at vnf:VNFC:boot-data, fields 'users' and 'config-files' are not compatible with 'user-data'", - HTTP_Bad_Request) + # if "boot-data" in vnfc: + # # check that user-data is incompatible with users and config-files + # if (vnfc["boot-data"].get("users") or vnfc["boot-data"].get("config-files")) and vnfc["boot-data"].get("user-data"): + # raise NfvoException( + # "Error at vnf:VNFC:boot-data, fields 'users' and 'config-files' are not compatible with 'user-data'", + # HTTP_Bad_Request) #check if the info in external_connections matches with the one in the vnfcs name_list=[] @@ -1839,10 +1839,10 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc def unify_cloud_config(cloud_config_preserve, cloud_config): - ''' join the cloud config information into cloud_config_preserve. + """ join the cloud config information into cloud_config_preserve. In case of conflict cloud_config_preserve preserves - None is admited - ''' + None is allowed + """ if not cloud_config_preserve and not cloud_config: return None @@ -1892,10 +1892,19 @@ def unify_cloud_config(cloud_config_preserve, cloud_config): new_cloud_config["boot-data-drive"] = cloud_config_preserve["boot-data-drive"] # user-data - if cloud_config and cloud_config.get("user-data") != None: - new_cloud_config["user-data"] = cloud_config["user-data"] - if cloud_config_preserve and cloud_config_preserve.get("user-data") != None: - new_cloud_config["user-data"] = cloud_config_preserve["user-data"] + new_cloud_config["user-data"] = [] + if cloud_config and cloud_config.get("user-data"): + if isinstance(cloud_config["user-data"], list): + new_cloud_config["user-data"] += cloud_config["user-data"] + else: + new_cloud_config["user-data"].append(cloud_config["user-data"]) + if cloud_config_preserve and cloud_config_preserve.get("user-data"): + if isinstance(cloud_config_preserve["user-data"], list): + new_cloud_config["user-data"] += cloud_config_preserve["user-data"] + else: + new_cloud_config["user-data"].append(cloud_config_preserve["user-data"]) + if not new_cloud_config["user-data"]: + del new_cloud_config["user-data"] # config files new_cloud_config["config-files"] = [] diff --git a/osm_ro/vimconn.py b/osm_ro/vimconn.py index a669612b..7adaa366 100644 --- a/osm_ro/vimconn.py +++ b/osm_ro/vimconn.py @@ -387,7 +387,8 @@ class vimconnector(): 'users': (optional) list of users to be inserted, each item is a dict with: 'name': (mandatory) user name, 'key-pairs': (optional) list of strings with the public key to be inserted to the user - 'user-data': (optional) string is a text script to be passed directly to cloud-init + 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init, + or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file 'config-files': (optional). List of files to be transferred. Each item is a dict with: 'dest': (mandatory) string with the destination absolute path 'encoding': (optional, by default text). Can be one of: diff --git a/osm_ro/vimconn_openstack.py b/osm_ro/vimconn_openstack.py index 4ac58286..3eb2990b 100644 --- a/osm_ro/vimconn_openstack.py +++ b/osm_ro/vimconn_openstack.py @@ -35,6 +35,7 @@ import netaddr import time import yaml import random +import sys from novaclient import client as nClient, exceptions as nvExceptions from keystoneauth1.identity import v2, v3 @@ -50,8 +51,11 @@ from httplib import HTTPException from neutronclient.neutron import client as neClient from neutronclient.common import exceptions as neExceptions from requests.exceptions import ConnectionError +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText -'''contain the openstack virtual machine status to openmano status''' + +"""contain the openstack virtual machine status to openmano status""" vmStatus2manoFormat={'ACTIVE':'ACTIVE', 'PAUSED':'PAUSED', 'SUSPENDED': 'SUSPENDED', @@ -685,6 +689,41 @@ class vimconnector(vimconn.vimconnector): except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: self._format_exception(e) + @staticmethod + def _create_mimemultipart(content_list): + """Creates a MIMEmultipart text combining the content_list + :param content_list: list of text scripts to be combined + :return: str of the created MIMEmultipart. If the list is empty returns None, if the list contains only one + element MIMEmultipart is not created and this content is returned + """ + if not content_list: + return None + elif len(content_list) == 1: + return content_list[0] + combined_message = MIMEMultipart() + for content in content_list: + if content.startswith('#include'): + format = 'text/x-include-url' + elif content.startswith('#include-once'): + format = 'text/x-include-once-url' + elif content.startswith('#!'): + format = 'text/x-shellscript' + elif content.startswith('#cloud-config'): + format = 'text/cloud-config' + elif content.startswith('#cloud-config-archive'): + format = 'text/cloud-config-archive' + elif content.startswith('#upstart-job'): + format = 'text/upstart-job' + elif content.startswith('#part-handler'): + format = 'text/part-handler' + elif content.startswith('#cloud-boothook'): + format = 'text/cloud-boothook' + else: # by default + format = 'text/x-shellscript' + sub_message = MIMEText(content, format, sys.getdefaultencoding()) + combined_message.attach(sub_message) + return combined_message.as_string() + def __wait_for_vm(self, vm_id, status): """wait until vm is in the desired status and return True. If the VM gets in ERROR status, return false. @@ -883,14 +922,17 @@ class vimconnector(vimconn.vimconnector): #cloud config userdata=None config_drive = None + userdata_list = [] if isinstance(cloud_config, dict): if cloud_config.get("user-data"): - userdata=cloud_config["user-data"] + if isinstance(cloud_config["user-data"], str): + userdata_list.append(cloud_config["user-data"]) + else: + for u in cloud_config["user-data"]: + userdata_list.append(u) if cloud_config.get("boot-data-drive") != None: config_drive = cloud_config["boot-data-drive"] if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"): - if userdata: - raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'") userdata_dict={} #default user if cloud_config.get("key-pairs"): @@ -924,8 +966,9 @@ class vimconnector(vimconn.vimconnector): if file.get("owner"): file_info["owner"] = file["owner"] userdata_dict["write_files"].append(file_info) - userdata = "#cloud-config\n" - userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False) + userdata_list.append("#cloud-config\n" + yaml.safe_dump(userdata_dict, indent=4, + default_flow_style=False)) + userdata = self._create_mimemultipart(userdata_list) self.logger.debug("userdata: %s", userdata) elif isinstance(cloud_config, str): userdata = cloud_config diff --git a/osm_ro/vimconn_vmware.py b/osm_ro/vimconn_vmware.py index 7fd7f9d1..9faf8b40 100644 --- a/osm_ro/vimconn_vmware.py +++ b/osm_ro/vimconn_vmware.py @@ -4024,7 +4024,8 @@ class vimconnector(vimconn.vimconnector): 'users': (optional) list of users to be inserted, each item is a dict with: 'name': (mandatory) user name, 'key-pairs': (optional) list of strings with the public key to be inserted to the user - 'user-data': (optional) string is a text script to be passed directly to cloud-init + 'user-data': (optional) can be a string with the text script to be passed directly to cloud-init, + or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file 'config-files': (optional). List of files to be transferred. Each item is a dict with: 'dest': (mandatory) string with the destination absolute path 'encoding': (optional, by default text). Can be one of: -- 2.17.1