Skip to content

Commit

Permalink
Rd 2266 update azure vm (#194)
Browse files Browse the repository at this point in the history
* virtual machine update

* style fix

* bump version

* add code for compare vm configurations

* move virtual machine tasks file to folder

* fix bug in get property and deep search in dictionary keys

* remove comments and style fixes

* add tests

* fix log message

* __NODOCS__

* added comment regarding upgrade of the api version
  • Loading branch information
AdarShaked authored May 30, 2021
1 parent f82f5ef commit 424636e
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
3.4.0: Update vm when using external resource.
3.3.1: RD-2472 - cluster_name property maintained for forward compatibility.
3.3.0: Add create_if_missing for all azure resources.
3.2.1: Fix bug, fail if trying to use external resource that doesn't exist.
Expand Down
Empty file added __init__.py
Empty file.
4 changes: 4 additions & 0 deletions cloudify_azure/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
API_VER_STORAGE_FILE_SHARE = '2019-06-01'
API_VER_NETWORK = '2016-09-01'
API_VER_NETWORK_LB_BACKEND_PROBES = '2020-03-01'
# When upgrading Api version to greater than 2019_12_01 version, update
# cloudify_azure/resources/compute/virtualmachine/virtualmachine_utils.py
# ::check_if_configuration_changed props list,
# according to api version.
API_VER_COMPUTE = '2016-03-30'
API_VER_STORAGE_BLOB = '2015-12-11'
API_VER_CONTAINER = '2017-07-01'
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from cloudify.decorators import operation

from cloudify_azure import (constants, decorators, utils)
from cloudify_azure.resources.compute.virtualmachine.virtualmachine_utils \
import check_if_configuration_changed
from cloudify_azure.resources.network.publicipaddress import PUBLIC_IP_PROPERTY
from azure_sdk.resources.network.public_ip_address import PublicIPAddress
from azure_sdk.resources.compute.virtual_machine import VirtualMachine
Expand Down Expand Up @@ -228,17 +230,22 @@ def _handle_userdata(ctx, existing_userdata):
return final_userdata


@operation(resumable=True)
@decorators.with_generate_name(VirtualMachine)
@decorators.with_azure_resource(VirtualMachine)
def create(ctx, args=None, **_):
def _create_or_update(ctx, args=None):
"""Uses an existing, or creates a new, Virtual Machine"""
azure_config = utils.get_client_config(ctx.node.properties)
name = utils.get_resource_name(ctx)
resource_group_name = utils.get_resource_group(ctx)
api_version = \
ctx.node.properties.get('api_version', constants.API_VER_COMPUTE)
virtual_machine = VirtualMachine(azure_config, ctx.logger, api_version)
resource_create_payload = _get_vm_create_or_update_payload(ctx, args, name)
_create_update_resource(resource_group_name,
name,
virtual_machine,
resource_create_payload)


def _get_vm_create_or_update_payload(ctx, args, name):
res_cfg = ctx.node.properties.get("resource_config", {})
spot_instance = res_cfg.pop("spot_instance", None)
# Build storage profile
Expand Down Expand Up @@ -332,26 +339,59 @@ def create(ctx, args=None, **_):
# Remove custom_data from os_profile if empty to avoid Errors.
elif 'custom_data' in resource_create_payload['os_profile']:
del resource_create_payload['os_profile']['custom_data']
# Create a resource (if necessary)

return resource_create_payload


def _create_update_resource(resource_group_name,
name,
vm_iface,
resource_create_payload):
try:
result = \
virtual_machine.create_or_update(resource_group_name, name,
resource_create_payload)
vm_iface.create_or_update(resource_group_name, name,
resource_create_payload)
except CloudError as cr:
raise cfy_exc.NonRecoverableError(
"create virtual_machine '{0}' "
"failed with this error : {1}".format(name,
cr.message)
)
)

utils.save_common_info_in_runtime_properties(resource_group_name,
name,
result)


@operation(resumable=True)
@decorators.with_generate_name(VirtualMachine)
@decorators.with_azure_resource(VirtualMachine)
def create(ctx, args=None, **_):
"""Uses an existing, or creates a new, Virtual Machine"""
_create_or_update(ctx, args)


ctx.instance.runtime_properties['resource_group'] = resource_group_name
ctx.instance.runtime_properties['resource'] = result
ctx.instance.runtime_properties['resource_id'] = result.get("id", "")
@operation(resumable=True)
def configure(ctx, args=None, **_):
azure_config = utils.get_client_config(ctx.node.properties)
name = utils.get_resource_name(ctx)
resource_group_name = utils.get_resource_group(ctx)
api_version = \
ctx.node.properties.get('api_version', constants.API_VER_COMPUTE)
virtual_machine = VirtualMachine(azure_config, ctx.logger, api_version)
payload = _get_vm_create_or_update_payload(ctx, args, name)
ctx.logger.debug("create_payload: \n {payload}".format(payload=payload))
current_vm = ctx.instance.runtime_properties["resource"]
if check_if_configuration_changed(ctx, payload, current_vm):
ctx.logger.info("configuration changed!!")
_create_update_resource(resource_group_name,
name,
virtual_machine,
payload)


@operation(resumable=True)
def configure(ctx, command_to_execute, file_uris, type_handler_version='1.8',
**_):
def start(ctx, command_to_execute, file_uris, type_handler_version='1.8', **_):
"""Configures the resource"""
azure_config = utils.get_client_config(ctx.node.properties)
resource_group_name = utils.get_resource_group(ctx)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# #######
# Copyright (c) 2016-2020 Cloudify Platform Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from cloudify import ctx


def check_if_configuration_changed(ctx, update_payload, current_vm):
for prop in ['location', 'tags', 'plan', 'availability_set',
'eviction_policy', 'billing_profile', 'priority',
'hardware_profile']:
update_property_value = update_payload.get(prop)
current_vm_property_value = current_vm.get(prop)
if update_property_value and ordered(
update_property_value) != ordered(current_vm_property_value):
ctx.logger.info("{prop} changed.".format(prop=prop))
ctx.logger.info("update payload: {content}.".format(
content=update_property_value))
ctx.logger.info("current configuration: {content}.".format(
content=current_vm_property_value))
return True

for prop in ['os_profile', 'storage_profile', 'network_profile']:
if prop == 'network_profile' and update_payload.get(prop):
update_property_value = update_payload.get(prop).as_dict()
else:
update_property_value = update_payload.get(prop, {})

current_vm_property_value = current_vm.get(prop, {})
if diff_dictionaries(update_property_value, current_vm_property_value):
ctx.logger.info("{prop} changed.".format(prop=prop))
return True

return False


def diff_dictionaries(update_dict, current_conf_dict):
"""
Returns True if update_dict has changes in a key that doesn't appear in
current_conf_dict.
current_conf_dict can have additional keys and its not considered as a
diff.
"""
for key in update_dict:
if isinstance(update_dict.get(key), dict):
res = diff_dictionaries(update_dict.get(key),
current_conf_dict.get(key, {}))
if res:
return True
elif ordered(update_dict.get(key)) != ordered(
current_conf_dict.get(key)):
ctx.logger.info(
'Changes found in diff_dictionaries: key={key}\n'.format(
key=key))
ctx.logger.info(
'update_dict: {}'.format(ordered(update_dict.get(key))))
ctx.logger.info(
'current_conf_dict: {}'.format(ordered(
current_conf_dict.get(key))))
return True
return False


def ordered(obj):
"""
This function will recursively sort any lists it finds
(and convert dictionaries to lists of (key, value) pairs so that they're
orderable)
"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
if isinstance(obj, list):
return sorted(ordered(x) for x in obj)
if isinstance(obj, str):
return obj.lower()
if isinstance(obj, (int, float)):
return str(obj)
else:
return obj
2 changes: 2 additions & 0 deletions cloudify_azure/resources/network/loadbalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def configure(ctx, **_):
@operation(resumable=True)
def delete(ctx, **_):
"""Deletes a Load Balancer"""
if ctx.node.properties.get('use_external_resource', False):
return
# Delete the resource
azure_config = utils.get_client_config(ctx.node.properties)
resource_group_name = utils.get_resource_group(ctx)
Expand Down
2 changes: 2 additions & 0 deletions cloudify_azure/resources/network/networkinterfacecard.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ def start(ctx, **_):
def delete(ctx, **_):
"""Deletes a Network Interface Card"""
# Delete the resource
if ctx.node.properties.get('use_external_resource', False):
return
azure_config = utils.get_client_config(ctx.node.properties)
resource_group_name = utils.get_resource_group(ctx)
name = ctx.instance.runtime_properties.get('name')
Expand Down
6 changes: 4 additions & 2 deletions cloudify_azure/tests/resources/test_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from msrestazure.azure_exceptions import CloudError

from cloudify_azure import utils
from cloudify_azure.resources.compute import (availabilityset, virtualmachine)
from cloudify_azure.resources.compute import (availabilityset)
from cloudify_azure.resources.compute.virtualmachine import virtualmachine


def return_none(foo):
Expand Down Expand Up @@ -187,7 +188,8 @@ def setUp(self):
}

@mock.patch('cloudify_azure.resources.compute.virtualmachine.'
'build_network_profile', side_effect=return_none)
'virtualmachine.build_network_profile',
side_effect=return_none)
def test_create(self, _, client, credentials):
self.node.properties['azure_config'] = self.dummy_azure_credentials
resource_group = 'sample_resource_group'
Expand Down
Loading

0 comments on commit 424636e

Please sign in to comment.