diff --git a/.circleci/config.yml b/.circleci/config.yml index 52a21a8b..2c1dcec5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - checkout - run: name: install python/pip/libvirt packages - command: sudo apt-get install -yq python-libvirt libvirt-dev python-dev python-pip + command: sudo apt-get install -yq python-dev python-pip - run: name: Download pip command: curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d451fffa..e202f496 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,7 @@ +2.15.0: + - Support Solaris guests (OmniOS CE) + - Wait IP flag for wait any ip's on connected NIC + - Set extra properties set to VM 2.14.0: - Export host and datastore for external VM 2.13.1: diff --git a/plugin.yaml b/plugin.yaml index 5732b195..facfab1a 100755 --- a/plugin.yaml +++ b/plugin.yaml @@ -7,8 +7,8 @@ plugins: vsphere: executor: central_deployment_agent package_name: cloudify-vsphere-plugin - package_version: '2.14.0' - source: https://github.com/cloudify-cosmo/cloudify-vsphere-plugin/archive/2.14.0.zip + package_version: '2.15.0' + source: https://github.com/cloudify-cosmo/cloudify-vsphere-plugin/archive/2.15.0.zip data_types: @@ -168,6 +168,14 @@ node_types: description: | Cdrom image path default: false + extra_config: + description: | + Extra config to set, key-value dictionary + default: {} + wait_ip: + description: | + Use guest exported ip as default. + default: false vm_folder: description: | Folder name for place new created VM diff --git a/setup.py b/setup.py index aadadc17..60cc8ffc 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setuptools.setup( zip_safe=True, name='cloudify-vsphere-plugin', - version='2.14.0', + version='2.15.0', packages=[ 'vsphere_plugin_common', 'vsphere_server_plugin', diff --git a/system_tests/resources/linux/content_library.yaml b/system_tests/resources/linux/content_library.yaml index 9e33963f..d888f540 100644 --- a/system_tests/resources/linux/content_library.yaml +++ b/system_tests/resources/linux/content_library.yaml @@ -46,9 +46,6 @@ inputs: type: string default: true - test_network_distributed: - default: false - template_library: type: string description: > diff --git a/system_tests/resources/linux/templates/CentOS-7-x86_64-GenericCloud-ContainerHost.ovf b/system_tests/resources/linux/templates/CentOS-7-x86_64-GenericCloud-ContainerHost.ovf index add5b6cc..0454ecd0 100644 --- a/system_tests/resources/linux/templates/CentOS-7-x86_64-GenericCloud-ContainerHost.ovf +++ b/system_tests/resources/linux/templates/CentOS-7-x86_64-GenericCloud-ContainerHost.ovf @@ -5,6 +5,18 @@ List of the virtual disks + diff --git a/system_tests/resources/solaris/content_library.yaml b/system_tests/resources/solaris/content_library.yaml new file mode 100644 index 00000000..86fa70fb --- /dev/null +++ b/system_tests/resources/solaris/content_library.yaml @@ -0,0 +1,221 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +imports: + - http://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml + - https://raw.githubusercontent.com/cloudify-incubator/cloudify-utilities-plugin/1.13.0/plugin.yaml + - ../../../plugin.yaml + +inputs: + + vcenter_user: + type: string + + vcenter_password: + type: string + + vcenter_ip: + type: string + + vcenter_port: + type: string + default: 443 + + vcenter_datacenter: + type: string + description: > + vcenter datacenter + default: Datacenter + + vcenter_resource_pool: + description: > + Resource pool name + default: Resources + + vcenter_datastore: + type: string + description: > + vcenter datastore + default: datastore1 + + vcenter_hypervisor_host: + type: string + description: > + vcenter host + + vsphere_auto_placement: + type: string + default: true + + template_library: + type: string + description: > + "Custom Solaris template library" + default: "Solaris" + + template_name: + type: string + description: > + "Custom Solaris template template name" + default: "Solaris" + + vm_ip: + type: string + description: > + VM ip + default: 172.16.168.156 + + vm_netmask: + type: string + description: > + VM netmask + default: 255.255.255.0 + + vm_gateway: + type: string + description: > + VM gateway + default: 172.16.168.1 + + vm_user: + type: string + description: > + VM user + default: root + + vm_password: + type: string + description: > + VM password + default: root + +############################################################################### +# DSL section +############################################################################### +dsl_definitions: + + connection_config: &connection_config + username: { get_input: vcenter_user } + password: {get_input: vcenter_password } + host: { get_input: vcenter_ip } + port: { get_input: vcenter_port } + datacenter_name: {get_input: vcenter_datacenter } + resource_pool_name: { get_input: vcenter_resource_pool } + auto_placement: { get_input: vsphere_auto_placement } + allow_insecure: true + +node_templates: + + vm_folder: + type: cloudify.vsphere.nodes.VMFolder + properties: + use_external_resource: true + name: vm + connection_config: *connection_config + + resource_pool: + type: cloudify.vsphere.nodes.ResourcePool + properties: + use_external_resource: true + name: { get_input: vcenter_resource_pool } + connection_config: *connection_config + + datastore: + type: cloudify.vsphere.nodes.Datastore + properties: + use_external_resource: true + name: { get_input: vcenter_datastore } + connection_config: *connection_config + + hypervisor_host: + type: cloudify.vsphere.nodes.Host + properties: + use_external_resource: true + name: { get_input: vcenter_hypervisor_host } + connection_config: *connection_config + + network: + type: cloudify.vsphere.nodes.Network + properties: + use_external_resource: true + network: + name: Internal + switch_distributed: false + connection_config: *connection_config + + vm_content_library: + type: cloudify.vsphere.nodes.ContentLibraryDeployment + properties: + library_name: { get_input: template_library } + template_name: { get_input: template_name } + connection_config: *connection_config + interfaces: + cloudify.interfaces.lifecycle: + create: + inputs: + target: + folder_id: { get_attribute: [vm_folder, vsphere_vm_folder_id] } + host_id: { get_attribute: [hypervisor_host, vsphere_hypervisor_host_id] } + resource_pool_id: { get_attribute: [resource_pool, vsphere_resource_pool_id] } + deployment_spec: + default_datastore_id: { get_attribute: [datastore, vsphere_datastore_id] } + network_mappings: + - key: 'VM Network' + value: { get_attribute: [network, vsphere_network_id, 0] } + relationships: + - target: datastore + type: cloudify.relationships.depends_on + - target: vm_folder + type: cloudify.relationships.depends_on + - target: resource_pool + type: cloudify.relationships.depends_on + - target: hypervisor_host + type: cloudify.relationships.depends_on + - target: network + type: cloudify.relationships.depends_on + + vm_instance: + type: cloudify.vsphere.nodes.Server + properties: + use_external_resource: true + connection_config: *connection_config + os_family: solaris + wait_ip: true + agent_config: + install_method: none + interfaces: + cloudify.interfaces.lifecycle: + start: + inputs: + server: + name: { get_attribute: [vm_content_library, vm_name] } + extra_config: + machine.id: { concat: [ 'ip=', { get_input: vm_ip }, '&netmask=', { get_input: vm_netmask }, '&gateway=', { get_input: vm_gateway } ] } + stop: + inputs: + force_stop: true + delete: + inputs: + force_delete: true + relationships: + - target: vm_content_library + type: cloudify.relationships.depends_on + + state_check: + type: cloudify.terminal.raw + interfaces: + cloudify.interfaces.lifecycle: + create: + inputs: + terminal_auth: + user: { get_input: vm_user } + password: { get_input: vm_password } + ip: { get_attribute: [vm_instance, ip] } + exit_command: quit + promt_check: + - '>' + calls: + - action: show ip + save_to: ips + relationships: + - target: vm_instance + type: cloudify.relationships.depends_on diff --git a/vsphere_plugin_common/__init__.py b/vsphere_plugin_common/__init__.py index f5125ab3..d51e6d51 100755 --- a/vsphere_plugin_common/__init__.py +++ b/vsphere_plugin_common/__init__.py @@ -1474,13 +1474,22 @@ def _update_vm(self, server, cdrom_image=None, remove_networks=False): devices.append(cdrom_device) return devices - def update_server(self, server, cdrom_image=None): + def update_server(self, server, cdrom_image=None, extra_config=None): # Attrach cdrom image to vm without change networks list devices_changes = self._update_vm(server, cdrom_image=cdrom_image, remove_networks=False) - if devices_changes: + if devices_changes or extra_config: spec = vim.vm.ConfigSpec() - spec.deviceChange = devices_changes + # changed devices + if devices_changes: + spec.deviceChange = devices_changes + # add extra config + if extra_config and isinstance(extra_config, dict): + logger().debug('Extra config: {config}' + .format(config=repr(extra_config))) + for k in extra_config: + spec.extraConfig.append( + vim.option.OptionValue(key=k, value=extra_config[k])) task = server.obj.ReconfigVM_Task(spec=spec) self._wait_for_task(task) @@ -1508,6 +1517,7 @@ def create_server( allowed_datastores=None, cdrom_image=None, vm_folder=None, + extra_config=None, ): logger().debug( "Entering create_server with parameters %s" @@ -1614,6 +1624,14 @@ def create_server( clonespec.powerOn = True clonespec.template = False + # add extra config + if extra_config and isinstance(extra_config, dict): + logger().debug('Extra config: {config}' + .format(config=repr(extra_config))) + for k in extra_config: + clonespec.extraConfig.append( + vim.option.OptionValue(key=k, value=extra_config[k])) + if adaptermaps: logger().debug( 'Preparing OS customization spec for {server}'.format( @@ -1682,11 +1700,17 @@ def create_server( options.changeSID = True options.deleteAccounts = False customspec.options = options + elif os_type == 'solaris': + ident = None + logger().info( + 'Customization of the Solaris OS is unsupported by ' + ' vSphere. Guest additions are required/supported.') else: ident = None logger().info( - 'os_type {os_type} was specified, but only "windows" and ' - '"linux" are supported. Customization is unsupported.' + 'os_type {os_type} was specified, but only "windows", ' + '"solaris" and "linux" are supported. Customization is ' + 'unsupported.' .format(os_type=os_type) ) diff --git a/vsphere_server_plugin/server.py b/vsphere_server_plugin/server.py index 0d2eee64..85a2a3a9 100644 --- a/vsphere_server_plugin/server.py +++ b/vsphere_server_plugin/server.py @@ -190,6 +190,7 @@ def create_new_server( os_family='linux', cdrom_image=None, vm_folder=None, + extra_config=None, ): vm_name = get_vm_name(ctx, server, os_family) ctx.logger.info( @@ -298,7 +299,8 @@ def create_new_server( allowed_clusters, allowed_datastores, cdrom_image=cdrom_image, - vm_folder=vm_folder) + vm_folder=vm_folder, + extra_config=extra_config) ctx.logger.info('Successfully created server called {name}'.format( name=vm_name)) ctx.instance.runtime_properties[VSPHERE_SERVER_ID] = server_obj._moId @@ -327,6 +329,7 @@ def start( enable_start_vm=True, cdrom_image=None, vm_folder=None, + extra_config=None, ): ctx.logger.debug("Checking whether server exists...") @@ -368,9 +371,12 @@ def start( os_family=os_family, cdrom_image=cdrom_image, vm_folder=vm_folder, + extra_config=extra_config ) else: - server_client.update_server(server=server_obj, cdrom_image=cdrom_image) + server_client.update_server(server=server_obj, + cdrom_image=cdrom_image, + extra_config=extra_config) if enable_start_vm: ctx.logger.info("Server already exists, powering on.") server_client.start_server(server=server_obj) @@ -577,7 +583,7 @@ def delete(ctx, server_client, server, os_family, force_delete): @op @with_server_client -def get_state(ctx, server_client, server, networking, os_family): +def get_state(ctx, server_client, server, networking, os_family, wait_ip): server_obj = get_server_by_context(ctx, server_client, server, os_family) if server_obj is None: raise NonRecoverableError( @@ -599,6 +605,7 @@ def get_state(ctx, server_client, server, networking, os_family): ctx.logger.info("Server is running, getting network details.") ctx.logger.info("Guest info: {info}" .format(info=repr(server_obj.guest))) + networks = networking.get('connect_networks', []) if networking else [] manager_network_ip = None management_networks = \ @@ -611,10 +618,16 @@ def get_state(ctx, server_client, server, networking, os_family): ctx.logger.info("Server management networks: {networks}" .format(networks=repr(management_networks))) + # used for guest ip checks + default_ip = None # We must obtain IPs at this stage, as they are not populated until # after the VM is fully booted for network in server_obj.guest.net: network_name = network.network + # save ip as default + if not default_ip: + default_ip = get_ip_from_vsphere_nic_ips(network) + # check management if management_network_name and \ (network_name == management_network_name): manager_network_ip = get_ip_from_vsphere_nic_ips(network) @@ -643,14 +656,13 @@ def get_state(ctx, server_client, server, networking, os_family): ) ctx.instance.runtime_properties[NETWORKS] = nets - try: - ctx.instance.runtime_properties[IP] = ( - manager_network_ip or - get_ip_from_vsphere_nic_ips(server_obj.guest.net[0]) - ) - except IndexError: + ctx.instance.runtime_properties[IP] = manager_network_ip or default_ip + if not ctx.instance.runtime_properties[IP]: ctx.logger.warn("Server has no IP addresses.") - ctx.instance.runtime_properties[IP] = None + # wait for any ip before next steps + if wait_ip: + ctx.logger.info("Waiting ip export from guest.") + return False if len(server_obj.guest.net): public_ips = [