netscaler / ansible-collection-netscaleradc

Custom Ansible modules for NetScaler ADC and NetScaler ADM. Part of NetScaler Automation Toolkit | https://github.com/netscaler/automation-toolkit
https://netscaler.github.io/ansible-collection-netscaleradc/
MIT License
114 stars 56 forks source link

support for binding policies to existing cs vserver (imperative style) #54

Closed fortinj66 closed 6 years ago

fortinj66 commented 6 years ago

Trying to bind a policy to a cs virtual server and fails with the following. I am using the latest master git pull...

johnf@os-master-d01 netscaler-ansible-modules]$ git log
commit f93065ae175fb021d8d61445ffa881a4013334b7
Merge: 37ec8d1 a15514a
Author: George Nikolopoulos <giorgos.nikolopoulos@citrix.com>
Date:   Wed May 2 20:29:34 2018 +0300

    Merge pull request #49 from citrix/fix_readme

    Fix documentation for Python SDK installation process

When I run the ansible task I get the following:

fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "name": "dev.validate-connection-micro.policy",
            "nitro_pass": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "nitro_user": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "nsip": "10.102.0.3",
            "state": "present",
            "targetlbvserver": "dev.validate-connection-micro.http",
            "targetvserver": "mwgso-api.marketamerica.com_http",
            "validate_certs": false
        }
    },
    "msg": "Unsupported parameters for (netscaler_cs_action) module: targetvserver Supported parameters include: comment, instance_ip, mas_proxy_call, name, nitro_auth_token, nitro_pass, nitro_protocol, nitro_timeout, nitro_user, nsip, save_config, state, targetlbvserver, targetvserverexpr, validate_certs"
}

The code and the docs indicate that it should exist:

DOCUMENTATION = '''
---
module: netscaler_cs_action
short_description: Manage content switching actions
description:
    - Manage content switching actions
    - This module is intended to run either on the ansible  control node or a bastion (jumpserver) with access to the actual netscaler instance

version_added: "2.4.0"

author: George Nikolopoulos (@giorgos-nikolopoulos)

options:

    name:
        description:
            - >-
                Name for the content switching action. Must begin with an ASCII alphanumeric or underscore C(_)
                character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon
                C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the content
                switching action is created.

    targetlbvserver:
        description:
            - "Name of the load balancing virtual server to which the content is switched."

    targetvserver:
        description:
            - "Name of the VPN virtual server to which the content is switched."

    targetvserverexpr:
        description:
            - "Information about this content switching action."

    comment:
        description:
            - "Comments associated with this cs action."

extends_documentation_fragment: netscaler
requirements:
    - nitro python sdk
'''

What is the correct way to add a binding if this module does not do it?

--John

chiradeep commented 6 years ago

The documentation is wrong currently. Can you paste the playbook yaml? Trying to understand what you're trying to achieve.

fortinj66 commented 6 years ago

How do I do the equivalent of:

echo "Binding mwgso-api.marketamerica.com_http to ${VIP}.policy”

${SSH} bind cs vserver mwgso-api.marketamerica.com_http -policyName ${VIP}.policy -targetLBVserver ${VIP}.http -priority ${PRIORITY}

This is what I was expecting to work:

chiradeep commented 6 years ago

See https://github.com/citrix/netscaler-ansible-modules/blob/f93065ae175fb021d8d61445ffa881a4013334b7/samples/content_switch_ssl_lb_mon.yaml#L130 for an example

fortinj66 commented 6 years ago

I did see that.

My worry is that if I use it to add a new binding, the current bindings would be deleted.

chiradeep commented 6 years ago

Looks like you're attempting to manage only part of the configuration using Ansible? If so, I'm not sure it is a great idea. Also, the 'targetvserver' option is only for SSL VPN servers. You should only need to specify 'targetLbvserver' in your scenario

fortinj66 commented 6 years ago

That puzzles me as all of our internal vservers are http (non-ssl) and we have to use the target vserver from the command line.

From my bash script:

${SSH} bind cs vserver mwgso-api.marketamerica.com_http -policyName ${VIP}.policy -targetLBVserver ${VIP}.http -priority ${PRIORITY}

and if you do the bind from the UI, you also have to select the target vserver before you can bind the target LBVserver...

The snippet abaove was a small part of the ansible script. The object of this script is to add a new microservice to the target vserver defined below. I should be able to add these one at a time. I have a bash script which works perfectly but doesn't integrate well into our deployment strategy.

This is the complete script:

- hosts: localhost
  gather_facts: false
  vars:
    kubeconfig: ./kubeconfig
    env: "none"

    type: "java"

    os_host_dev: "https://os-lb-d01.maeagle.corp:8443"
    context_dev: "dev-microservices/os-lb-d01-maeagle-corp:8443/system:serviceaccount:internal-systems:micro-config"
    api_key_dev: 

    os_host_prod: "https://console.prod.os.maeagle.corp:8443"
    context_prod: "default/console-prod-os-maeagle-corp:8443/system:serviceaccount:internal-systems:micro-config"
    api_key_prod: 
    os_env:  "{{env | regex_replace('^stag.*', 'stage') | regex_replace('^dev.*', 'dev') | regex_replace('^(prod|live).*', 'prod')}}"
    api_key: "{{os_env | regex_replace('(dev|stage)', api_key_dev) | regex_replace('prod', api_key_prod) }}"
    os_host: "{{os_env | regex_replace('(dev|stage)', os_host_dev) | regex_replace('prod', os_host_prod) }}"
    context: "{{os_env | regex_replace('(dev|stage)', context_dev) | regex_replace('prod', context_prod) }}"
    prefix:  "{{os_env | regex_replace('stage', 'stg') | regex_replace('prod', 'prd')}}"
    namespace: "{{os_env +'-microservices'}}"
    service_name: "none"

    env_prefix: "{{os_env | regex_replace('(dev|stage)', 'd') | regex_replace('prod', 'p') }}"

    heartbeat: "{{type | regex_replace('java', 'GET /'+service_name+'/application.wadl') | regex_replace('python', 'GET /'+service_name+'/heartbeat') }}"

    service: "{{lookup('openshift', kubeconfig=kubeconfig, context=context, api_key=api_key, host=os_host, verify_ssl='no', api_version='v1', kind='Service', namespace=namespace, resource_name=service_name)}}"

    node_port: "{{service.spec.ports[0].node_port}}"
    vip:  "{{prefix + '.'  + service_name}}"
    servicegroupname: "{{vip + '.' + node_port}}"
    vserver_name: "{{vip + '.' + 'http'}}"
    policyname: "{{vip + '.' + 'policy'}}"

    targetvserver: "mwgso-api.marketamerica.com_http"

  tasks:
    - name: Check for valid environment
      fail:
        msg: "Environment '{{env}}' not defined or invalid. Add '-e env=[dev|staging|prod|live]'"
      when: env|regex_search('^(dev|staging|prod|live)') is none

    - name: Check for valid service
      fail:
        msg: "Service {{service_name}} does not exist"
      when: service|length == 0

    - name: Set lb monitor
      netscaler_lb_monitor:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
        state: present
        monitorname: "{{vip}}"
        type: HTTP
        respcode: ['200']
        resptimeout: 5
        httprequest: "{{heartbeat}}"
        lrtm: disabled
        interval: 30

    - name: Setup http service group
      netscaler_servicegroup:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
        state: present
        servicegroupname: "{{servicegroupname}}"
        servicetype: HTTP
        cip: enabled
        cipheader: X-Forwarded-For
        cka: NO
        cmp: YES
        clttimeout: 180
        svrtimeout: 360
        maxclient: 0
        maxreq: 0
        tcpb: NO
        useproxyport: YES
        usip: NO
        servicemembers:
          - servername: "{{'os-node-'+env_prefix+'01'}}"
            port: "{{node_port}}"
          - servername: "{{'os-node-'+env_prefix+'02'}}"
            port: "{{node_port}}"
          - servername: "{{'os-node-'+env_prefix+'03'}}"
            port: "{{node_port}}"
          - servername: "{{'os-node-'+env_prefix+'04'}}"
            port: "{{node_port}}"
        monitorbindings:
          - monitorname: "{{vip}}"

    - name: Create a load balancing vserver bound to services
      netscaler_lb_vserver:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
        state: present
        name: "{{vserver_name}}"
        servicetype: HTTP
        clttimeout: 180
        ipv46: 0.0.0.0
        port: 0
        persistencetype: NONE
        servicegroupbindings:
            - servicegroupname: "{{servicegroupname}}"

    - name: Create url cs policy
      netscaler_cs_policy:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
        state: present
        policyname: "{{policyname}}"
        rule: "HTTP.REQ.IS_VALID && HTTP.REQ.HOSTNAME.CONTAINS(\"{{env}}\") && HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH(\"/{{service_name}}\")"

    - name: Configure netscaler content switching action
      netscaler_cs_action:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
        state: present
        name: "{{policyname}}"
        targetlbvserver: "{{vserver_name}}"
        targetvserver: "{{targetvserver}}"

    - name: Save configuration
      netscaler_save_config:
        nsip: 10.102.0.3
        nitro_user: xxx
        nitro_pass: xxx
        validate_certs: no
fortinj66 commented 6 years ago

As I suspected, the module netscaler_cs_vserver.py will delete anything not configured in the playbook

That's a problem if all I want to do is add a new one.

def sync_cs_policybindings(client, module):
    log('Syncing cs policybindings')
    actual_bindings = get_actual_policybindings(client, module)
    configured_bindings = get_configured_policybindings(client, module)

    # Delete actual bindings not in configured
    delete_keys = list(set(actual_bindings.keys()) - set(configured_bindings.keys()))
    for key in delete_keys:
        log('Deleting binding for policy %s' % key)
        csvserver_cspolicy_binding.delete(client, actual_bindings[key])

    # Add configured bindings not in actual
    add_keys = list(set(configured_bindings.keys()) - set(actual_bindings.keys()))
    for key in add_keys:
        log('Adding binding for policy %s' % key)
        configured_bindings[key].add()

    # Update existing if changed
    modify_keys = list(set(configured_bindings.keys()) & set(actual_bindings.keys()))
    for key in modify_keys:
        if not configured_bindings[key].has_equal_attributes(actual_bindings[key]):
            log('Updating binding for policy %s' % key)
            csvserver_cspolicy_binding.delete(client, actual_bindings[key])
            configured_bindings[key].add()
chiradeep commented 6 years ago

The way to use cs action is to specify it as the action within a cs policy. For example:

add cs vserver test_csvserver HTTP 192.168.10.1 80
add lb vserver test_lbvserver
add cs action -targetLbvserver test_lbvserver
add cs policy test_cspolicy -rule HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH("/test") -action test_csaction
bind cs vserver test_csvserver -policyname test_cspolicy -priority 200

the targetvserver in the cs action is not a cs vserver AFAIK, and is only used in SSL VPN.

if your lb vserver is named consistently according to the URL fragment, then you could create a cs action that selects the lb vserver based on the pattern.

add cs action mycsaction1 -targetvserverExpr '"mylb_" + HTTP.REQ.URL.SUFFIX

chiradeep commented 6 years ago

could you add all the existing policies to the playbook and manage all policies using Ansible?

fortinj66 commented 6 years ago

Unfortunately, adding all the policies would be unmanageable. I have over 100 current policies running...

This is the methodology that I use from a script to accomplish the task:

${SSH} add serviceGroup ${VIP}.${PORT} HTTP -maxClient 0 -maxReq 0 -cip ENABLED X-Forwarded-For -usip NO -useproxyport YES -cltTimeout 180 -svrTimeout 360 -CKA NO -TCPB NO -CMP YES

for node in ${NODES}; do
  echo "Binding service group ${VIP}.${PORT} to ${node}"
  ${SSH} bind serviceGroup ${VIP}.${PORT} ${node} ${PORT}
done

${SSH} add lb vserver ${VIP}.http HTTP 0.0.0.0 0 -persistenceType NONE -cltTimeout 180
${SSH} bind lb vserver ${VIP}.http ${VIP}.${PORT}
${SSH} add lb monitor ${VIP} HTTP -respCode 200 -httpRequest \"GET /${SERVICE}/application.wadl\" -LRTM DISABLED -interval 30 -resptimeout 5
${SSH} "add cs policy ${VIP}.policy -rule \"HTTP.REQ.IS_VALID && HTTP.REQ.HOSTNAME.CONTAINS(\\\"${ENV}\\\") && HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH(\\\"/${SERVICE}\\\")\""
${SSH} bind cs vserver mwgso-api.marketamerica.com_http -policyName ${VIP}.policy -targetLBVserver ${VIP}.http -priority ${PRIORITY}
${SSH} save nsconfig
chiradeep commented 6 years ago

if you adopt a naming scheme with the lb vserver named as lb_${SERVICE} then you may be able to use cs action add cs action test_csaction -targetvserverExpr "lb_" + HTTP.REQ.URL.PATH.GET(1)

You would have only 1 policy to be bound to the cs vserver regardless of ${SERVICE}. add cs policy test_cspolicy -rule \"HTTP.REQ.IS_VALID && HTTP.REQ.HOSTNAME.CONTAINS(\\\"${ENV}\\\") -action test_csaction bind cs vserver mwgso-api.marketamerica.com_http -policyName test_cspolicy

In Ansible terms you wouldn't configure any policies or actions since these are one-time configuration.

fortinj66 commented 6 years ago

I'm going to play with this a bit, but it may be easier to fork this and just add an option add/delete/sync rather than assume a sync...

chiradeep commented 6 years ago

I wrote a small script to export your existing policies into a playbook. See if it helps https://gist.github.com/chiradeep/b111a44cfc2bfb94fcbee6d7db35dab1

chiradeep commented 6 years ago

if you want the imperative style, I suggest enhancing the netscaler_nitro_request which supports such a mode. Currently it supports add/remove/rename and doesn't support bind/unbind

chiradeep commented 6 years ago

@fortinj66 should I close this?

fortinj66 commented 6 years ago

For this particular case probably...

That being said, I still feel you should be able to add/delete a bind on an individual policy (especially since almost every other object can be handled individually) but the current framework seems to makes that difficult.

However, I was able to make the cs_action functionality work on our test systems to that will probable be my final solution...

Thanks for you help and insight into this issue.

--John

chiradeep commented 6 years ago

thanks for the feedback. opened a new issue https://github.com/citrix/netscaler-ansible-modules/issues/56