Initial injection of decort_pfw and collateral changes to other modules

master
Sergey Shubin svs1370 5 years ago
parent e5e6db6586
commit 125ebb1fb8

@ -241,7 +241,7 @@ from ansible.module_utils.decort_utils import *
def decort_disk_package_facts(disk_facts, check_mode=False): def decort_disk_package_facts(disk_facts, check_mode=False):
"""Package a dictionary of disk facts according to the decort_vins module specification. """Package a dictionary of disk facts according to the decort_disk module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of This dictionary will be returned to the upstream Ansible engine at the completion of
the module run. the module run.
@ -329,13 +329,6 @@ def decort_disk_parameters():
workflow_context=dict(type='str', required=False), workflow_context=dict(type='str', required=False),
) )
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the ViNS with this id or name exists under specified account / resource group
# 3) if ViNS does not exist -> deploy
# 4) if ViNS exists: check desired state, desired configuration -> initiate action(s) accordingly
# 5) report result to Ansible
def main(): def main():
module_parameters = decort_disk_parameters() module_parameters = decort_disk_parameters()

@ -107,7 +107,7 @@ options:
required: no required: no
id: id:
description: description:
- ID of the VM. - ID of the KVM VM to manage.
- 'Either I(id) or a combination of VM name I(name) and RG related parameters (either I(rg_id) or a pair of - 'Either I(id) or a combination of VM name I(name) and RG related parameters (either I(rg_id) or a pair of
I(account_name) and I(rg_name) is required to manage an existing VM.' I(account_name) and I(rg_name) is required to manage an existing VM.'
- 'This parameter is not required (and ignored) when creating new VM as VM ID is assigned by cloud platform - 'This parameter is not required (and ignored) when creating new VM as VM ID is assigned by cloud platform

@ -0,0 +1,322 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2020 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: decort_disk
short_description: Manage network Port Forward rules for Compute instances in DECORT cloud
description: >
This module can be used to create new port forwarding rules in DECORT cloud platform,
modify and delete them.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 2.6
- PyJWT module
- requests module
- decort_utils utility library (module)
- DECORT cloud platform version 3.4.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
account_id:
description:
- ID of the account, which owns this disk. This is the alternative to I(account_name) option.
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
default: 0
required: no
account_name:
description:
- 'Name of the account, which will own this disk.'
- 'This parameter is ignored if I(account_id) is specified.'
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
compute_id:
description:
- ID of the Compute instance to manage network port forwarding rules for.
required: yes
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
rules:
description:
- 'Set of rules to configure for the Compute instance identidied by I(compute_id) in the virtual
network segment identidied by I(vins_id).'
- The set is specified as a list of dictionaries with the following structure:
- ' - (int) public_port_start - starting port number on the ViNS external interface.'
- ' - (int) public_port_end - optional end port number of the ViNS external interface. If not specified
or set equal to I(public_port_start), a one-to-one rule is created. Otherwise a ranged rule will
be created, which maps specified external port range to local ports starting from I(local_port).'
- ' - (int) local_port - port number on the local interface of the Compute. For ranged rule it is
interpreted as a base port to translate public port range to internal port range.'
- ' - (string) porot - protocol, specify either I(tcp) or I(udp).'
state:
description:
- 'Specify the desired state of the port forwarding rules set for the Compute instance identified by
I(compute_id).'
- 'If I(state=present), the rules will be applied according to the I(rules) parameter.'
- 'If I(state=absent), all rules for the specified Compute instance will be deleted regardless of
I(rules) parameter.'
default: present
choices: [ absent, present ]
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
vins_id:
description:
- ID of the virtual network segment (ViNS), where port forwarding rules will be set up.
- This ViNS must have connection to external network.
- Compute instance specified by I(compute_id) must be connected to this ViNS.
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
'''
EXAMPLES = '''
- name: configure one-toone rule for SSH protocol on Compute ID 100 connected to ViNS ID 5.
decort_pfw:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://cloud.digitalenergy.online"
compute_id: 100
vins_id: 5
rules:
- public_port_start: 10022
local_port: 22
proto: tcp
state: present
delegate_to: localhost
register: my_pfw
'''
RETURN = '''
facts:
description: facts about created PFW rules
returned: always
type: dict
sample:
facts:
compute_id: 100
vins_id: 5
rules:
-
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
def decort_pfw_package_facts(pfw_facts, check_mode=False):
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@param (dict) pfw_facts: dictionary with PFW facts as returned by API call to .../???/get
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
comp_id=0,
vins_id=0,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
if pfw_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['comp_id'] = pfw_facts['com_id']
return ret_dict
def decort_pfw_parameters():
"""Build and return a dictionary of parameters expected by decort_pfw module in a form accepted
by AnsibleModule utility class."""
return dict(
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
compute_id=dict(type='int', required=True),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
rules=dict(type='list', required=False, default=[]),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
def main():
module_parameters = decort_pfw_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
)
decon = DecortController(amodule)
pfw_facts = None # will hold PFW facts
#
# Validate module arguments:
# 1) specified Compute instance exists in correct state
# 2) specified ViNS exists
# 3) ViNS has GW function
# 4) Compute is connected to this ViNS
#
validated_comp_id, comp_facts, rg_id = decon.compute_find(amodule.params['comp_id'])
if not validated_comp_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['comp_id'])
amodule.fail_json(**decon.result)
validated_vins_id, vins_facts = decon.vins_find(amodule.params['vins_id'])
if not validated_vins_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**decon.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
decon.result['failed'] = True
decon.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id)
amodule.fail_json(**decon.result)
#
# Initial validation of module arguments is complete
#
if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None)
else:
# manage PFW rules accodring to the module arguments
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
#
# complete module run
#
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...)
decon.result['facts'] = decort_pfw_package_facts(pfw_facts, amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()

@ -645,157 +645,6 @@ class DecortController(object):
return ret_comp_id, ret_comp_dict, validated_rg_id return ret_comp_id, ret_comp_dict, validated_rg_id
def vm_portforwards(self, arg_vm_dict, arg_pfw_specs):
"""Manage VM port forwarding rules in a smart way. This method takes desired port forwarding rules as
an argument and compares it with the existing port forwarding rules
@param arg_vm_dict: dictionary with VM facts. It identifies the VM for which network configuration is
requested.
@param arg_pfw_specs: desired network specifications.
"""
#
#
# Strategy for port forwards management:
# 1) obtain current port forwarding rules for the target VM
# 2) create a delta list of port forwards (rules to add and rules to remove)
# - full match between existing & requested = ignore, no update of pfw_delta
# - existing rule not present in requested list => copy to pfw_delta and mark as 'delete'
# - requested rule not present in the existing list => copy to pfw_delta and mark as 'create'
# 3) provision delta list (first delete rules marked for deletion, next add rules mark for creation)
#
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vm_portforwards")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['changed'] = False
self.result['msg'] = "vm_portforwards() in check mode: port forwards configuration change requested."
return
pfw_api_base = "/restmachine/cloudapi/portforwarding/"
pfw_api_params = dict(rgId=arg_vm_dict['rgId'],
machineId=arg_vm_dict['id'])
api_resp = self.decort_api_call(requests.post, pfw_api_base + "list", pfw_api_params)
existing_pfw_list = json.loads(api_resp.content.decode('utf8'))
if not len(arg_pfw_specs) and not len(existing_pfw_list):
# Desired & existing port forwarding rules both empty - exit
self.result['failed'] = False
self.result['msg'] = ("vm_portforwards(): new and existing port forwarding lists both are empty - "
"nothing to do. No change applied to VM ID {}.").format(arg_vm_dict['id'])
return
# pfw_delta_list will be a list of dictionaries that describe _changes_ to the port forwarding rules
# that existed for the target VM at the moment we entered this method.
# The dictionary has the following keys:
# ext_port - integer, external port number
# int_port - integer, internal port number
# proto - string, either 'tcp' or 'udp'
# action - string, either 'delete' or 'create'
# id - the ID of existing port forwarding rule that should be deleted (applicable when action='delete')
# NOTE: not all keys may exist in the resulting list!
pfw_delta_list = []
# Mark all requested pfw rules as new - if we find a match later, we will mark corresponding rule
# as 'new'=False
for requested_pfw in arg_pfw_specs:
requested_pfw['new'] = True
for existing_pfw in existing_pfw_list:
existing_pfw['matched'] = False
for requested_pfw in arg_pfw_specs:
# TODO: portforwarding API needs refactoring.
# NOTE!!! Another glitch in the API implementation - .../portforwarding/list returns port numbers as strings,
# while .../portforwarding/create expects them as integers!!!
# Also: added type casting to int for requested_pfw in case the value comes as string from a complex
# variable in a loop
if (int(existing_pfw['publicPort']) == int(requested_pfw['ext_port']) and
int(existing_pfw['localPort']) == int(requested_pfw['int_port']) and
existing_pfw['protocol'] == requested_pfw['proto']):
# full match - existing rule stays as is:
# mark requested rule spec as 'new'=False, existing rule spec as 'macthed'=True
requested_pfw['new'] = False
existing_pfw['matched'] = True
# Scan arg_pfw_specs, find all records that have been marked 'new'=True, then copy them the pfw_delta_list
# marking as action='create'
for requested_pfw in arg_pfw_specs:
if requested_pfw['new']:
pfw_delta = dict(ext_port=requested_pfw['ext_port'],
int_port=requested_pfw['int_port'],
proto=requested_pfw['proto'],
action='create')
pfw_delta_list.append(pfw_delta)
# Scan existing_pfw_list, find all records that have 'matched'=False, then copy them to pfw_delta_list
# marking as action='delete'
for existing_pfw in existing_pfw_list:
if not existing_pfw['matched']:
pfw_delta = dict(ext_port=int(existing_pfw['publicPort']),
int_port=int(existing_pfw['localPort']),
proto=existing_pfw['protocol'],
action='delete')
pfw_delta_list.append(pfw_delta)
if not len(pfw_delta_list):
# nothing to do
self.result['failed'] = False
self.result['msg'] = ("vm_portforwards() no difference between current and requested port "
"forwarding rules found. No change applied to VM ID {}.").format(arg_vm_dict['id'])
return
# Need VDC facts to extract VDC external IP - it is needed to create new port forwarding rules
# Note that in a scenario when VM and VDC are created in the same task we may arrive to here
# when VDC is still in DEPLOYING state. Attempt to configure port forward rules in this will generate
# an error. So we have to check VDC status and loop for max ~60 seconds here so that the newly VDC
# created enters DEPLOYED state
max_retries = 5
retry_counter = max_retries
while retry_counter > 0:
_, vdc_facts = self.vdc_find(arg_rg_id=arg_vm_dict['rgId'])
if vdc_facts['status'] == "DEPLOYED":
break
retry_timeout = 5 + 10 * (max_retries - retry_counter)
time.sleep(retry_timeout)
retry_counter = retry_counter - 1
if vdc_facts['status'] != "DEPLOYED":
# We still cannot manage port forwards due to incompatible VDC state. This is not necessarily an
# error that should lead to the task failure, so we register this fact in the module message and
# return from the method.
#
# self.result['failed'] = True
self.result['msg'] = ("vm_portforwards(): target VDC ID {} is still in '{}' state, "
"setting port forwarding rules is not possible.").format(arg_vm_dict['rgId'],
vdc_facts['status'])
return
# Iterate over pfw_delta_list and first delete port forwarding rules marked for deletion,
# next create the rules marked for creation.
sorted_pfw_delta_list = sorted(pfw_delta_list, key=lambda i: i['action'], reverse=True)
for pfw_delta in sorted_pfw_delta_list:
if pfw_delta['action'] == 'delete':
pfw_api_params = dict(rgId=arg_vm_dict['rgId'],
publicIp=vdc_facts['externalnetworkip'],
publicPort=pfw_delta['ext_port'],
proto=pfw_delta['proto'])
self.decort_api_call(requests.post, pfw_api_base + 'deleteByPort', pfw_api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
elif pfw_delta['action'] == 'create':
pfw_api_params = dict(rgId=arg_vm_dict['rgId'],
publicIp=vdc_facts['externalnetworkip'],
publicPort=pfw_delta['ext_port'],
machineId=arg_vm_dict['id'],
localPort=pfw_delta['int_port'],
protocol=pfw_delta['proto'])
self.decort_api_call(requests.post, pfw_api_base + 'create', pfw_api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
self.result['changed'] = True
return
def compute_powerstate(self, comp_facts, target_state, force_change=True): def compute_powerstate(self, comp_facts, target_state, force_change=True):
"""Manage Compute power state transitions or its guest OS restarts. """Manage Compute power state transitions or its guest OS restarts.
@ -2322,9 +2171,6 @@ class DecortController(object):
return ret_disk_id, ret_disk_dict return ret_disk_id, ret_disk_dict
#
# TODO: method is not fully implemented - need ../disks/list or ../disks/search API function!
#
def disk_find(self, disk_id, disk_name="", account_id=0, check_state=False): def disk_find(self, disk_id, disk_name="", account_id=0, check_state=False):
"""Find specified Disk. """Find specified Disk.
@ -2510,3 +2356,158 @@ class DecortController(object):
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = True self.result['changed'] = True
return return
##############################
#
# Port Forward rules management
#
##############################
def pfw_configure(self, comp_facts, vins_facts, new_rules=None):
"""Manage port forwarding rules for Compute in a smart way. The method will try to match existing
rules against the new rules set and calculate the delta settings to apply to the corresponding
virtual network function.
@param (dict) comp_facts: dictionary with Compute facts as returned by .../compute/get. It describes
the Compute instance for which PFW rules will be managed.
@param (dict) vins_facts: dictionary with ViNS facts as returned by .../vins/get. It described ViNS
to which PFW rules set will be applied.
@param (list of dicts) new_rules: new PFW rules set. If None is passed, remove all existing
PFW rules for the Compute.
"""
# At the entry to this method we assume that initial validations are already passed, namely:
# 1) Compute instance exists
# 2) ViNS exists and has GW VNS in valid state
# 3) Compute is connected to this ViNS
#
#
# Strategy for port forwards management:
# 1) obtain current port forwarding rules for the target VM
# 2) create a delta list of port forwards (rules to add and rules to remove)
# - full match between existing & requested = ignore, no update of pfw_delta
# - existing rule not present in requested list => copy to pfw_delta and mark as 'delete'
# - requested rule not present in the existing list => copy to pfw_delta and mark as 'create'
# 3) provision delta list (first delete rules marked for deletion, next add rules mark for creation)
#
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "pfw_configure")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("pfw_configure() in check mode: port forwards configuration requested "
"for Compute ID {} / ViNS ID {}").format(comp_facts['id'], vins_facts['id'])
return None
iface_ipaddr = "" # keep IP address associated with Compute's connection to this ViNS - need this for natRuleDel API
for iface in comp_facts['interfaces']:
if iface['connType'] == 'VXLAN' and iface['connId'] == vins_facts['vxlanId']:
iface_ipaddr = iface['ipAddress']
break
else:
decon.result['failed'] = True
decon.result['msg'] = "Compute ID {} is not connected to ViNS ID {}.".format(comp_facts['id'], vins_facts['id'])
return None
existing_rules = []
for runner in vins_facts['vnfs']['NAT']['config']['rules']:
if runner['vmId'] == comp_facts['id']:
existing_rules.append(runner)
if not len(existing_rules) and not len(new_rules):
self.result['failed'] = False
self.result['warning'] = ("pfw_configure(): both existing and new port forwarding rule lists "
"for Compute ID {} are empty - nothing to do.").format(comp_facts['id'])
return None
if not len(new_rules):
# delete all existing rules for this Compute
api_params = dict(vinsId=vins_facts['id'])
for runner in existing_rules:
api_params['ruleId'] = runner['id']
self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/natRuleDel", api_params)
self.result['failed'] = False
self.result['chnaged'] = True
return None
#
# delta_list will be a list of dictionaries that describe _changes_ to the port forwarding rules
# of the Compute in hands.
# The dictionary has the following keys - values:
# (int) publicPortStart - external port range start
# (int) publicPortEnd - external port range end
# (int) localPort - internal port number
# (string) protocol - protocol, either 'tcp' or 'udp'
# (string) action - string, either 'del' or 'add'
# (int) id - the ID of existing PFW rule that should be deleted (applicable only for action='del')
#
delta_list = []
# select from new_rules the rules to add - those not found in existing rules
for rule in new_rules:
rule['action'] = 'add'
rule_port_end = rule.get('public_port_end', rule['public_port_start'])
for runner in existing_rules:
if (runner['publicPortStart'] == rule['public_port_start'] and
runner['publicPortEnd'] == rule_port_end and
runner['localPort'] == rule['local_port'] and
runner['protocol'] == rule['proto']):
rule['action'] = 'keep'
break
if rule['action'] == 'add':
delta_rule = dict(publicPortStart=rule['public_port_start'],
publicPortEnd=rule_port_end,
localPort=rule['local_port'],
protocol=rule['proto'],
action='add',
id='-1')
delta_list.append(delta_rule)
# select from existing_rules the rules to delete - those not found in new_rules
for rule in existing_rules:
rule['action'] = 'del'
for runner in new_rules:
runner_port_end = runner.get('public_port_end', runner['public_port_start'])
if (rule['publicPortStart'] == runner['public_port_start'] and
rule['publicPortEnd'] == runner_port_end and
rule['localPort'] == runner['local_port'] and
rule['protocol'] == runner['proto']):
rule['action'] = 'keep'
break
if rule['action'] == 'del':
delta_list.append(rule)
if not len(delta_list):
# strange, but still nothing to do?
self.result['failed'] = False
self.result['warning'] = ("pfw_configure() no difference between current and new PFW rules "
"found. No change applied to Compute ID {}.").format(comp_facts['id'])
return
# now delta_list contains a list of enriched rule dictionaries with extra key 'action', which
# tells what kind of action is expected on this rule - 'add' or 'del'
# We first iterate to delete, then iterate again to add rules
# Iterate over pfw_delta_list and first delete port forwarding rules marked for deletion,
# next create the rules marked for creation.
api_base = "/restmachine/cloudapi/vins/"
for delta_rule in sorted(delta_list, key=lambda i: i['action'], reverse=True):
if delta_rule['action'] == 'del':
api_params = dict(vinsId=vins_facts['id'],
ruleId=delta_rule['id'])
self.decort_api_call(requests.post, api_base + 'natRuleDel', api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
elif delta_rule['action'] == 'add':
api_params = dict(vinsId=vins_facts['id'],
intIp=iface_ipaddr,
intPort=delta_rule['localPort'],
extPortStart=delta_rule['publicPortStart'],
extPortEnd=delta_rule['publicPortEnd'],
proto=delta_rule['protocol'])
self.decort_api_call(requests.post, api_base + 'natRuleAdd', api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
self.result['changed'] = True
return
Loading…
Cancel
Save