Extend contrail fromula

This patch extends contrail states/modules and client to
be able of:
  * Create physical_routers
  * Create tor switches
  * Create ports
  * Create logical_interfaces
  * Create Virtual Machine Interfaces

Related-Prod: PROD-14409

Change-Id: Ia4b8390c077e4590bb9ecc53e44d15f53d8a7c95
diff --git a/README.rst b/README.rst
index 54e9502..f76fb4c 100644
--- a/README.rst
+++ b/README.rst
@@ -590,11 +590,32 @@
       compute:
         gateway_mode: server
 
+TSN nodes
+---------
+
+Configure TSN nodes
+
+.. code-block:: yaml
+
+  opencontrail:
+    compute:
+      enabled: true
+      tor:
+        enabled: true
+        bind:
+          port: 8086
+        agent:
+          tor01:
+            id: 0
+            port: 6632
+            host: 127.0.0.1
+            address: 127.0.0.1
+
 
 Set up metadata secret for the Vrouter
 -------------------------------------
 
-In order to get cloud-init within the instance to properly fetch 
+In order to get cloud-init within the instance to properly fetch
 instance metadata, metadata_proxy_secret in the Vrouter agent config
 should match the value in nova.conf. The administrator should define
 it in the pillar:
@@ -740,7 +761,7 @@
 --------------------------
 
 .. code-block:: yaml
-  
+
     database:
       ....
       bind:
@@ -953,6 +974,51 @@
            - 10.10.10.10
            ipf_port: 80
 
+Enforcing physical routers
+
+.. code-block:: yaml
+
+  opencontrail:
+    client:
+      ...
+      physical_router:
+        router1:
+          name: router1
+          dataplane_ip: 1.2.3.4
+          management_ip: 1.2.3.4
+          vendor_name: ovs
+          product_name: ovs
+          agents:
+           - tsn0-0
+           - tsn0
+
+Enforcing physical/logical interfaces for routers
+
+
+.. code-block:: yaml
+
+  opencontrail
+    client:
+    ...
+    physical_router:
+      router1:
+        ...
+        interface:
+          port1:
+            name: port1
+            logical_interface:
+              port1_l:
+                name: 'port1.0'
+                vlan_tag: 0
+                interface_type: L2
+                virtual_machine_interface:
+                  port1_port:
+                    name: port1_port
+                    ip_address: 192.168.90.107
+                    mac_address: '2e:92:a8:af:c2:21'
+                    security_group: 'default'
+                    virtual_network: 'virtual-network'
+
 
 Usage
 =====
@@ -1001,7 +1067,7 @@
 
 Display IF MAP table
 
-Look for neighbours, if VM has 2, it's ok 
+Look for neighbours, if VM has 2, it's ok
 
 	http://<control-node>:8083/Snh_IFMapTableShowReq?table_name=
 
diff --git a/_modules/contrail.py b/_modules/contrail.py
index f03ee33..8818922 100644
--- a/_modules/contrail.py
+++ b/_modules/contrail.py
@@ -16,6 +16,7 @@
 from netaddr import IPNetwork
 from vnc_api.vnc_api import PhysicalRouter, PhysicalInterface, LogicalInterface
 from vnc_api.vnc_api import EncapsulationPrioritiesType
+from vnc_api.vnc_api import VirtualMachineInterface, MacAddressesType
 
 try:
     from vnc_api import vnc_api
@@ -81,6 +82,17 @@
 
     return rt_inst_obj
 
+def _get_fq_name(vnc_client, resource_name, project_name, domain='default-domain'):
+    res = [domain]
+    if project_name:
+        res.append(project_name)
+    if resource_name:
+        res.append(resource_name)
+    return res
+
+def _get_project_obj(vnc_client, name, domain='default-domain'):
+    return vnc_client.project_read(fq_name=[domain, name])
+
 
 def _get_ip(ip_w_pfx):
     return str(IPNetwork(ip_w_pfx).ip)
@@ -501,7 +513,7 @@
 
 
 def logical_interface_create(name, parent_names, parent_type='physical-interface', vlan_tag=None, interface_type="l2",
-                             **kwargs):
+                             vmis=None, **kwargs):
     '''
     Create specific Contrail logical interface
 
@@ -514,6 +526,7 @@
     ret = {}
     vnc_client = _auth(**kwargs)
     gsc_obj = _get_config(vnc_client)
+
     liface_obj = logical_interface_get(name, parent_names, parent_type, **kwargs)
     if 'Error' not in liface_obj:
         return {'OK': 'Logical interface ' + name + ' already exists'}
@@ -538,7 +551,14 @@
             return {'Error': "Virtual Network have to be defined for L3 interface type"}
 
         liface_obj = LogicalInterface(name, parent_obj, vlan_tag, interface_type.lower())
+
+        for vmi_name, vmi in vmis.iteritems():
+            vmi = vnc_client.virtual_machine_interface_read(
+                fq_name=_get_fq_name(vnc_client, resource_name=vmi_name,
+                                     project_name=kwargs.get('tenant', 'admin')))
+            liface_obj.add_virtual_machine_interface(vmi)
         vnc_client.logical_interface_create(liface_obj)
+
     return "Created"
 
 
@@ -1193,12 +1213,76 @@
 
         salt '*' contrail.virtual_machine_interfaces
     '''
+    ret = []
+    vnc_client = _auth(**kwargs)
+    project = _get_project_obj(vnc_client, name=kwargs.get('tenant', 'admin'))
+    project_uuid = project.get_uuid()
+
+    vm_ifaces = vnc_client.virtual_machine_interfaces_list(
+        detail=True, parent_id=project_uuid)
+
+    for vm_iface in vm_ifaces:
+        ret.append(vm_iface.__dict__)
+
+    return ret
+
+
+def virtual_machine_interface_create(name,
+                                     virtual_network,
+                                     mac_address=None,
+                                     ip_address=None,
+                                     security_group=None,
+                                     **kwargs):
+    '''
+    Create specific Contrail  virtual machine interface (Port)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.virtual_machine_interface_create port01 net01 mac_address='01:02:03:04:05:06'
+        router_types:
+        - tor-agent
+        - tor-service-node
+        - embedded
+    '''
     ret = {}
     vnc_client = _auth(**kwargs)
-    vm_ifaces = vnc_client._objects_list('virtual-machine-interface', detail=True)
-    for vm_iface in vm_ifaces:
-        ret[vm_iface.name] = vm_iface.__dict__
-    return ret
+    project = _get_project_obj(vnc_client, name=kwargs.get('tenant', 'admin'))
+
+    vm_int = VirtualMachineInterface(name, parent_obj=project)
+
+    if mac_address:
+      mac_address_obj = MacAddressesType([mac_address])
+      vm_int.set_virtual_machine_interface_mac_addresses(mac_address_obj)
+
+    if security_group:
+      sgo = vnc_client.security_group_read(fq_name=_get_fq_name(
+          vnc_client, security_group, kwargs.get('tenant', 'admin')))
+      vm_int.set_security_group(sgo)
+
+    vnet_uuid = virtual_network_get(virtual_network, **kwargs)[virtual_network]['_uuid']
+    vnet_obj = vnc_client.virtual_network_read(id=vnet_uuid)
+    vm_int.set_virtual_network(vnet_obj)
+
+    vmi_uuid = vnc_client.virtual_machine_interface_create(vm_int)
+    vmi = vnc_client.virtual_machine_interface_read(id=vmi_uuid)
+
+    vm_int.set_port_security_enabled(False)
+    vnc_client.virtual_machine_interface_update(vm_int)
+
+    #Allocate IP to VMI
+    ip = vnc_api.InstanceIp(name + '.ip')
+    ip.set_virtual_machine_interface(vmi)
+    ip.set_virtual_network(vnet_obj)
+
+    ip_uuid = vnc_client.instance_ip_create(ip)
+
+    if ip_address:
+        ip.set_instance_ip_address(ip_address)
+        vnc_client.instance_ip_update(ip)
+
+    return vmi.__dict__
 
 
 def virtual_network_list(**kwargs):
@@ -1218,3 +1302,23 @@
     for virtual_network in virtual_networks:
         ret[virtual_network.name] = virtual_network.__dict__
     return ret
+
+
+def virtual_network_get(name, **kwargs):
+    '''
+    Return a specific Contrail virtual network
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' contrail.virtual_network_get net01
+    '''
+    ret = {}
+    vnet_objs = virtual_network_list(**kwargs)
+    if name in vnet_objs:
+        ret[name] = vnet_objs.get(name)
+    if len(ret) != 1 :
+        return {'result': False,
+                'Error': 'Error in retrieving virtual networks.'}
+    return ret
diff --git a/_states/contrail.py b/_states/contrail.py
index e64948e..c66333f 100644
--- a/_states/contrail.py
+++ b/_states/contrail.py
@@ -375,7 +375,8 @@
     return ret
 
 
-def logical_interface_present(name, parent_names, parent_type, vlan_tag=None, interface_type="L2", **kwargs):
+def logical_interface_present(name, parent_names, parent_type, vlan_tag=None, interface_type="L2",
+                              vmis=None, **kwargs):
     '''
     Ensures that the Contrail logical interface exists.
 
@@ -383,7 +384,8 @@
     :param parent_names:  	List of parents
     :param parent_type		Parent resource type. Any of ['physical-router', 'physical-interface']
     :param vlan_tag:		VLAN tag (.1Q) classifier for this logical interface.
-    :param interface_type	Logical interface type can be L2 or L3.
+    :param interface_type:	Logical interface type can be L2 or L3.
+    :param vmis:                Virtual machine interface name associate with
     '''
     ret = {'name': name,
            'changes': {},
@@ -394,7 +396,7 @@
         pass
     else:
         result = __salt__['contrail.logical_interface_create'](name, parent_names, parent_type, vlan_tag,
-                                                               interface_type, **kwargs)
+                                                               interface_type, vmis=vmis, **kwargs)
         if 'Error' in result:
             return False
 
@@ -593,3 +595,42 @@
         ret['comment'] = 'Database node {0} has been created'.format(name)
         ret['changes']['DatabaseNode'] = result
     return ret
+
+
+def virtual_machine_interface_present(name,
+                                      virtual_network,
+                                      mac_address=None,
+                                      ip_address=None,
+                                      security_group=None,
+                                       **kwargs):
+    '''
+    Ensures that the Contrail virtual machine interface exists.
+
+    :param name:             Virtual machine interface name
+    :param virtual_network:  Network name
+    :param mac_address:      Mac address of vmi interface
+    :param ip_address:       Virtual machine interface ip address
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Virtual machine interface "{0}" already exists'.format(name)}
+
+    vmis = __salt__['contrail.virtual_machine_interface_list'](**kwargs)
+
+    for vmi in vmis:
+      if vmi['name'] == name:
+        return ret
+
+    vmi = __salt__['contrail.virtual_machine_interface_create'](name, virtual_network,
+                                                                mac_address=mac_address,
+                                                                ip_address=ip_address,
+                                                                security_group=security_group,
+                                                                **kwargs)
+    if vmi['name'] == name:
+        ret['comment'] = 'Virtual machine interface {0} has been created'.format(name)
+        ret['result'] = True
+    else:
+        ret['comment'] = 'Virtual machine interface {0} creation failed'.format(name)
+        ret['result'] = False
+    return ret
diff --git a/opencontrail/client.sls b/opencontrail/client.sls
index 99b5bee..e467c27 100644
--- a/opencontrail/client.sls
+++ b/opencontrail/client.sls
@@ -134,4 +134,82 @@
 
 {%- endfor %}
 
+{%- for physical_router_name, physical_router in client.get('physical_router', {}).items() %}
+
+opencontrail_client_physical_router_{{ physical_router_name }}:
+  contrail.physical_router_present:
+  - name: {{ physical_router.name }}
+  - dataplane_ip: {{ physical_router.dataplane_ip }}
+  - management_ip: {{ physical_router.get('management_ip') }}
+  - vendor_name: {{ physical_router.get('vendor_name') }}
+  - product_name: {{ physical_router.get('product_name') }}
+  - vnc_managed: {{ physical_router.get('vnc_managed', True) }}
+  - agents: {{ physical_router.get('agents') }}
+  - 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: '/'
+
+{%- for physical_interface_name, physical_interface in physical_router.get('interface', {}).items() %}
+
+opencontrail_client_physical_interface_{{ physical_interface_name }}:
+  contrail.physical_interface_present:
+  - name: {{ physical_interface_name }}
+  - physical_router: {{ physical_router_name }}
+  - requires:
+    - opencontrail_client_physical_router_{{ physical_router_name }}
+
+
+{%- for logical_interface_name, logical_interface in physical_interface.get('logical_interface', {}).items() %}
+
+{%- for virtual_machine_interface_name, virtual_machine_interface in logical_interface.get('virtual_machine_interface', {}).items() %}
+
+opencontrail_client_virtual_machine_interface_{{ virtual_machine_interface_name }}:
+  contrail.virtual_machine_interface_present:
+  - name: {{ virtual_machine_interface.name }}
+  - virtual_network: {{ virtual_machine_interface.virtual_network }}
+  - ip_address: {{ virtual_machine_interface.get('ip_address') }}
+  - mac_address: {{ virtual_machine_interface.get('mac_address') }}
+  - security_group: {{ virtual_machine_interface.get('security_group') }}
+  - 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: '/'
+  - requires_in:
+    - opencontrail_client_logical_interface_{{ logical_interface_name }}
+
+{%- endfor %} # end for virtual_machine_interface
+
+opencontrail_client_logical_interface_{{ logical_interface_name }}:
+  contrail.logical_interface_present:
+  - name: {{ logical_interface.name }}
+  - parent_names:
+    - {{ physical_interface.name }}
+    - {{ physical_router.name }}
+  - parent_type: 'physical-interface'
+  - vlan_tag: {{ logical_interface.get('vlan_tag') }}
+  - interface_type: {{ logical_interface.get('interface_type', 'L2') }}
+  - vmis: {{ logical_interface.get('virtual_machine_interface', {}) }}
+  - 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: '/'
+  - requires:
+    - opencontrail_client_physical_interface_{{ physical_interface_name }}
+
+{%- endfor %} # end for logical_interaface
+
+{%- endfor %} # end for physical_interface
+
+{%- endfor %} # end for physical_router
+
 {%- endif %}