Add link local services provisioning support

Change-Id: I9a9721f8469e355c7187ab9c54338e70a62ab2b1
diff --git a/README.rst b/README.rst
index 7ad6f17..1e97333 100644
--- a/README.rst
+++ b/README.rst
@@ -849,6 +849,45 @@
         nal02:
           ip_address: 172.16.0.32
 
+Enforcing Link Local Services
+
+.. code-block:: yaml
+
+  opencontrail:
+    client:
+      ...
+      linklocal_service:
+         # example with dns name address (only one permited)
+         meta1:
+           lls_ip: 10.0.0.23
+           lls_port: 80
+           ipf_addresses: "meta.example.com"
+           ipf_port: 80
+         # example with multiple ip addresses
+         meta2:
+           lls_ip: 10.0.0.23
+           lls_port: 80
+           ipf_addresses:
+           - 10.10.10.10
+           - 10.20.20.20
+           - 10.30.30.30
+           ipf_port: 80
+         # example with one ip address
+         meta3:
+           lls_ip: 10.0.0.23
+           lls_port: 80
+           ipf_addresses:
+           - 10.10.10.10
+           ipf_port: 80
+         # example with name override
+         lls_meta4:
+           name: meta4
+           lls_ip: 10.0.0.23
+           lls_port: 80
+           ipf_addresses:
+           - 10.10.10.10
+           ipf_port: 80
+
 
 Usage
 =====
diff --git a/_modules/contrail.py b/_modules/contrail.py
index 2cd1747..11d7ca1 100644
--- a/_modules/contrail.py
+++ b/_modules/contrail.py
@@ -17,6 +17,8 @@
 
 try:
     from vnc_api import vnc_api
+    from vnc_api.vnc_api import LinklocalServiceEntryType, \
+        LinklocalServicesTypes, GlobalVrouterConfig
     from vnc_api.gen.resource_client import VirtualRouter, AnalyticsNode, \
         ConfigNode, DatabaseNode, BgpRouter
     from vnc_api.gen.resource_xsd import AddressFamilies, BgpSessionAttributes, \
@@ -504,3 +506,160 @@
     database_node_obj = databaseNode(name, gsc_obj)
     vnc_client.database_node_delete(
         fq_name=database_node_obj.get_fq_name())
+
+
+
+
+def _get_vrouter_config(vnc_client):
+    try:
+        config = vnc_client.global_vrouter_config_read(
+            fq_name=['default-global-system-config', 'default-global-vrouter-config'])
+    except Exception:
+        config = None
+
+    return config
+
+
+
+def linklocal_service_list(**kwargs):
+    '''
+    Return a list of all Contrail link local services
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.linklocal_service_list
+    '''
+    ret = {}
+    vnc_client = _auth(**kwargs)
+
+    current_config = _get_vrouter_config(vnc_client)
+    if current_config is None:
+        return ret
+
+    service_list_res = current_config.get_linklocal_services()
+    if service_list_res is None:
+        service_list_obj = {'linklocal_service_entry': []}
+    else:
+        service_list_obj = service_list_res.__dict__
+    for _, value in service_list_obj.iteritems():
+        for entry in value:
+            service = entry.__dict__
+            if 'linklocal_service_name' in service:
+                ret[service['linklocal_service_name']] = service
+    return ret
+
+
+def linklocal_service_get(name, **kwargs):
+    '''
+    Return a specific Contrail link local service
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.linklocal_service_get llservice
+    '''
+    ret = {}
+    services = linklocal_service_list(**kwargs)
+    if name in services:
+        ret[name] = services.get(name)
+    if len(ret) == 0:
+        return {'Error': 'Error in retrieving link local service "{0}"'.format(name)}
+    return ret
+
+
+def linklocal_service_create(name, lls_ip, lls_port, ipf_dns_or_ip, ipf_port, **kwargs):
+    '''
+    Create specific Contrail link local service
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.linklocal_service_create \
+            llservice 10.10.10.103 22 '["20.20.20.20", "30.30.30.30"]' 22
+        salt '*' contrail.linklocal_service_create \
+            llservice 10.10.10.103 22 link-local.service.dns-name 22
+    '''
+    ret = {}
+    vnc_client = _auth(**kwargs)
+
+    current_config = _get_vrouter_config(vnc_client)
+
+    service_entry = LinklocalServiceEntryType(
+        linklocal_service_name=name,
+        linklocal_service_ip=lls_ip,
+        linklocal_service_port=lls_port,
+        ip_fabric_service_port=ipf_port)
+    if isinstance(ipf_dns_or_ip, basestring):
+        service_entry.ip_fabric_DNS_service_name = ipf_dns_or_ip
+    elif isinstance(ipf_dns_or_ip, list):
+        service_entry.ip_fabric_service_ip = ipf_dns_or_ip
+        service_entry.ip_fabric_DNS_service_name = ''
+
+    if current_config is None:
+        new_services = LinklocalServicesTypes([service_entry])
+        new_config = GlobalVrouterConfig(linklocal_services=new_services)
+        vnc_client.global_vrouter_config_create(new_config)
+    else:
+        _current_service_list = current_config.get_linklocal_services()
+        if _current_service_list is None:
+            service_list = {'linklocal_service_entry': []}
+        else:
+            service_list = _current_service_list.__dict__
+        new_services = [service_entry]
+        for key, value in service_list.iteritems():
+            if key != 'linklocal_service_entry':
+                continue
+            for _entry in value:
+                entry = _entry.__dict__
+                if 'linklocal_service_name' in entry:
+                    if entry['linklocal_service_name'] == name:
+                        return {'Error': 'Link local service "{0}" already exists'.format(name)}
+                    new_services.append(_entry)
+            service_list[key] = new_services
+        new_config = GlobalVrouterConfig(linklocal_services=service_list)
+        vnc_client.global_vrouter_config_update(new_config)
+    ret = linklocal_service_list(**kwargs)
+    return ret[name]
+
+
+def linklocal_service_delete(name, **kwargs):
+    '''
+    Delete specific link local service entry
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.linklocal_service_delete llservice
+    '''
+    vnc_client = _auth(**kwargs)
+
+    current_config = _get_vrouter_config(vnc_client)
+
+    found = False
+    if current_config is not None:
+        _current_service_list = current_config.get_linklocal_services()
+        if _current_service_list is None:
+            service_list = {'linklocal_service_entry': []}
+        else:
+            service_list = _current_service_list.__dict__
+        new_services = []
+        for key, value in service_list.iteritems():
+            if key != 'linklocal_service_entry':
+                continue
+            for _entry in value:
+                entry = _entry.__dict__
+                if 'linklocal_service_name' in entry:
+                    if entry['linklocal_service_name'] == name:
+                        found = True
+                    else:
+                        new_services.append(_entry)
+            service_list[key] = new_services
+        new_config = GlobalVrouterConfig(linklocal_services=service_list)
+        vnc_client.global_vrouter_config_update(new_config)
+    if not found:
+        return {'Error': 'Link local service "{0}" not found'.format(name)}
diff --git a/_states/contrail.py b/_states/contrail.py
index f117667..17f579d 100644
--- a/_states/contrail.py
+++ b/_states/contrail.py
@@ -41,6 +41,53 @@
         name: cmp01
 
 
+Enforce the link local service entry existence
+----------------------------------------------
+
+.. code-block:: yaml
+
+    # Example with dns name, only one is permited
+    lls_meta1:
+      contrail.linklocal_service_present:
+        - name: meta1
+        - lls_ip: 10.0.0.23
+        - lls_port: 80
+        - ipf_addresses: "meta.example.com"
+        - ipf_port: 80
+
+    # Example with multiple ip addresses
+    lls_meta2:
+      contrail.linklocal_service_present:
+        - name: meta2
+        - lls_ip: 10.0.0.23
+        - lls_port: 80
+        - ipf_addresses:
+          - 10.10.10.10
+          - 10.20.20.20
+          - 10.30.30.30
+        - ipf_port: 80
+
+    # Example with one ip addresses
+    lls_meta3:
+      contrail.linklocal_service_present:
+        - name: meta3
+        - lls_ip: 10.0.0.23
+        - lls_port: 80
+        - ipf_addresses:
+          - 10.10.10.10
+        - ipf_port: 80
+
+
+Enforce the link local service entry absence
+--------------------------------------------
+
+.. code-block:: yaml
+
+    lls_meta1_delete:
+      contrail.linklocal_service_absent:
+        - name: cmp01
+
+
 Enforce the analytics node existence
 ------------------------------------
 
@@ -122,6 +169,46 @@
     return ret
 
 
+def linklocal_service_present(name, lls_ip, lls_port, ipf_addresses, ipf_port, **kwargs):
+    '''
+    Ensures that the Contrail link local service entry exists.
+
+    :param name:           Link local service name
+    :param lls_ip:         Link local ip address
+    :param lls_port:       Link local service port
+    :param ipf_addresses:  IP fabric dns name or list of IP fabric ip addresses
+    :param ipf_port:       IP fabric port
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Link local service "{0}" already exists'.format(name)}
+    lls = __salt__['contrail.linklocal_service_get'](name, **kwargs)
+    if 'Error' in lls:
+        __salt__['contrail.linklocal_service_create'](name, lls_ip, lls_port, ipf_addresses, ipf_port, **kwargs)
+        ret['comment'] = 'Link local service "{0}" has been created'.format(name)
+        ret['changes']['LinkLocalService'] = 'Created'
+    return ret
+
+
+def linklocal_service_absent(name, **kwargs):
+    '''
+    Ensure that the Contrail link local service entry doesn't exist
+
+    :param name: The name of the link local service entry
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': ' "{0}" is already absent'.format(name)}
+    lls = __salt__['contrail.linklocal_service_get'](name, **kwargs)
+    if 'Error' not in lls:
+        __salt__['contrail.linklocal_service_delete'](name, **kwargs)
+        ret['comment'] = 'Link local service "{0}" has been deleted'.format(name)
+        ret['changes']['LinkLocalService'] = 'Deleted'
+
+    return ret
+
 def analytics_node_present(name, ip_address, **kwargs):
     '''
     Ensures that the Contrail analytics node exists.
diff --git a/opencontrail/client.sls b/opencontrail/client.sls
index 61b999c..35fe914 100644
--- a/opencontrail/client.sls
+++ b/opencontrail/client.sls
@@ -99,4 +99,23 @@
 
 {%- endfor %}
 
+{%- for linklocal_service_name, linklocal_service in client.get('linklocal_service', {}).items() %}
+
+opencontrail_client_linklocal_service_{{ linklocal_service_name }}:
+  contrail.linklocal_service_present:
+  - name: {{ linklocal_service.get('name', linklocal_service_name) }}
+  - lls_ip: {{ linklocal_service.get('lls_ip') }}
+  - lls_port: {{ linklocal_service.get('lls_port') }}
+  - ipf_addresses: {{ linklocal_service.get('ipf_addresses') }}
+  - ipf_port: {{ linklocal_service.get('ipf_port') }}
+  - user: {{ client.identity.user }}
+  - password: {{ client.identity.password }}
+  - project: {{ client.identity.tenant }}
+  - auth_host_ip: {{ client.identity.host }}
+  - api_server_ip: {{ client.api.host }}
+  - api_server_port: {{ client.api.port }}
+  - api_base_url: '/'
+
+{%- endfor %}
+
 {%- endif %}