Rework virtual_network_create module

Add ability to update virtual network. Properties related with
VirtualNetworkType object were collected to separate dictionary.
Closes-bug: PROD-27467 (PROD:27467)

Change-Id: Iad94e2d35392ad703e5cdfe0458de6b1d7d332f9
diff --git a/_modules/contrail.py b/_modules/contrail.py
index c9741e3..c84b451 100644
--- a/_modules/contrail.py
+++ b/_modules/contrail.py
@@ -116,13 +116,6 @@
     return str(IPNetwork(ip_w_pfx).ip)
 
 
-def _create_floating_ip_pool(name, vn_obj, **kwargs):
-    vnc_client = _auth(**kwargs)
-    # create floating ip pool
-    fip_obj = FloatingIpPool(name=name, parent_obj=vn_obj)
-    vnc_client.floating_ip_pool_create(fip_obj)
-
-
 def virtual_router_list(**kwargs):
     '''
     Return a list of all Contrail virtual routers
@@ -1561,33 +1554,39 @@
     return ret
 
 
-def virtual_network_create(name, conf=None, **kwargs):
+def virtual_network_create(name, domain='default-domain', project='admin',
+                           ipam_domain='default-domain',
+                           ipam_project='default-project',
+                           ipam_name='default-network-ipam',
+                           router_external=None, route_target_list=None,
+                           subnet_conf=None, vntype_conf=None, **kwargs):
     '''
     Create Contrail virtual network
     CLI Example:
     .. code-block:: bash
-    salt '*' contrail.virtual_network_create name
 
-    salt.cmdRun(pepperEnv, 'ntw01*', 'salt-call contrail.virtual_network_create
-    "testicek" "{"external":"True","ip":"172.16.111.0","prefix":24,
-    "asn":64512,"target":10000}" ')
+    salt -C 'I@opencontrail:control:role:primary'
+        contrail.virtual_network_create public router_external=True
+        route_target_list=["target:64512:10000"]
+        subnet_conf=["ip_prefix":"172.16.111.0","ip_prefix_len":24]
 
     Parameters:
-    name required - name of the new network
-
-    conf (dict) optional:
-        domain (string) optional - which domain use for vn creation
-        project (string) optional - which project use for vn creation
-        ipam_domain (string) optional - domain for ipam
-        ipam_project (string) optional - project for ipam
-        ipam_name (string) optional - ipam name
+    name (str): name of the network (required)
+    domain (str): name of domain (optional)
+    project (str): name of project (optional)
+    ipam_domain (str): domain for ipam (optional)
+    ipam_project (str): project for ipam (optional)
+    ipam_name (str): name of IP Address Management (optional)
+    router_external (bool): When true, this virtual network is openstack router
+        external network. (optional)
+    route_target_list (list): route target list - format is
+        ['target:<asn>:<target>']
+    subnet_conf (dict):
         ip_prefix (string) optional - format is xxx.xxx.xxx.xxx
         ip_prefix_len (int) optional - format is xx
-        asn (int) optional - autonomus system number
-        target (int) optional - route target number
-        external (boolean) optional - set if network is external
-
+    vntype_conf (dict):
         allow_transit (boolean) optional - enable allow transit
+        vxlan_network_identifier (int) - VxLAN VNI value for network
         forwarding_mode (any of ['l2_l3','l2','l3']) optional
             - packet forwarding mode for this virtual network
         rpf (any of ['enabled','disabled']) optional
@@ -1596,112 +1595,164 @@
         mirror_destination (boolean) optional
             - Mark the vn as mirror destination network
     '''
-    if conf is None:
-        conf = {}
-
-    # check for domain, is missing set to default-domain
-    if 'domain' in conf:
-        vn_domain = str(conf['domain'])
-    else:
-        vn_domain = 'default-domain'
-    # check for project, is missing set to admin
-    if 'project' in conf:
-        vn_project = str(conf['project'])
-    else:
-        vn_project = 'admin'
-    # check for ipam domain,default is default-domain
-    if 'ipam_domain' in conf:
-        ipam_domain = str(conf['ipam_domain'])
-    else:
-        ipam_domain = 'default-domain'
-    # check for ipam domain,default is default-domain
-    if 'ipam_project' in conf:
-        ipam_project = str(conf['ipam_project'])
-    else:
-        ipam_project = 'default-project'
-
-    if 'ipam_name' in conf:
-        ipam_name = conf['ipam_name']
-    else:
-        ipam_name = 'default-network-ipam'
-
     ret = {'name': name,
            'changes': {},
            'result': True,
            'comment': ''}
 
-    # list of existing vn networks
-    vn_networks = []
     vnc_client = _auth(**kwargs)
-    prj_obj = vnc_client.project_read(fq_name=[vn_domain,
-                                               vn_project])
-    # check if the network exists
-    vn_networks_list = vnc_client._objects_list('virtual_network')
-    fq = [vn_domain, vn_project, name]
-    for network in vn_networks_list['virtual-networks']:
-        if fq == network['fq_name']:
-            ret['comment'] = ("Virtual network with name "
-                              + name + " already exists")
-            return ret
+    fq_name = [domain, project, name]
+    prj_obj = vnc_client.project_read(fq_name=[domain,
+                                               project])
+    ipam_obj = vnc_client.network_ipam_read(fq_name=[ipam_domain,
+                                                     ipam_project,
+                                                     ipam_name])
+    if route_target_list:
+        route_target_list = RouteTargetList(route_target_list)
+    if subnet_conf:
+        if 'ip_prefix' in subnet_conf and 'ip_prefix_len' in subnet_conf:
+            ipam_sn = IpamSubnetType(subnet=SubnetType(
+                ip_prefix=subnet_conf['ip_prefix'],
+                ip_prefix_len=subnet_conf['ip_prefix_len']))
 
-    vn_obj = VirtualNetwork(name, prj_obj)
-    vn_type_obj = VirtualNetworkType()
-    # get ipam from default project and domain
-    ipam = vnc_client.network_ipam_read(fq_name=[ipam_domain,
-                                                 ipam_project,
-                                                 ipam_name])
+    # Check if the network exists
+    vn_obj_list = vnc_client.virtual_networks_list(parent_id=prj_obj.uuid)
+    if name in list(map(lambda x: x['fq_name'][2],
+                        vn_obj_list['virtual-networks'])):
+        changes = {}
+        vn_obj = vnc_client.virtual_network_read(fq_name=fq_name)
 
-    # create subnet
-    if 'ip_prefix' in conf and 'ip_prefix_len' in conf:
-        ipam_subnet_type = IpamSubnetType(subnet=SubnetType(
-                                          ip_prefix=conf['ip_prefix'],
-                                          ip_prefix_len=conf['ip_prefix_len']))
+        # Update IPAM properties
+        ipam_refs = vn_obj.get_network_ipam_refs()
+        subnet_detected = False
+        if subnet_conf and ipam_refs:
+            for ipam in ipam_refs:
+                if ipam_obj.get_uuid() == ipam['uuid']:
+                    ipam_subnet_list = ipam['attr'].get_ipam_subnets()
+                    for ipam_subnet_type in ipam_subnet_list:
+                        subnet_obj = ipam_subnet_type.get_subnet()
+                        ip_prefix = subnet_obj.get_ip_prefix()
+                        ip_prefix_len = subnet_obj.get_ip_prefix_len()
+                        if subnet_conf['ip_prefix'] == ip_prefix and \
+                                subnet_conf['ip_prefix_len'] == ip_prefix_len:
+                            subnet_detected = True
+        if subnet_conf and not subnet_detected:
+            changes['ipam_subnet'] = \
+                {'added': '{0}/{1}'.format(subnet_conf['ip_prefix'],
+                                           subnet_conf['ip_prefix_len'])}
+            vn_obj.add_network_ipam(ipam_obj,
+                                    VnSubnetsType(ipam_subnets=[ipam_sn]))
 
-        vn_subnets_type_obj = VnSubnetsType(ipam_subnets=[ipam_subnet_type])
-        vn_obj.add_network_ipam(ipam, vn_subnets_type_obj)
+        # Update VirtualNetwork properties
+        external = vn_obj.get_router_external()
+        if router_external is not None and router_external != external:
+            changes['router_external'] = {'from': external,
+                                          'to': router_external}
+            vn_obj.set_router_external(router_external)
+        if route_target_list:
+            changes['router_list'] = \
+                {'from': str(vn_obj.get_route_target_list()),
+                 'to': str(route_target_list)}
+            vn_obj.set_route_target_list(route_target_list)
 
-    # add route target to the network
-    if 'asn' in conf and 'target' in conf:
-        route_target_list_obj = RouteTargetList(["target:{0}:{1}"
-                                                 .format(conf['asn'],
-                                                         conf['target'])])
-        vn_obj.set_route_target_list(route_target_list_obj)
+        # Update VirtualNetworkType properties
+        if vntype_conf:
+            vn_type_obj = vn_obj.get_virtual_network_properties()
+            if vn_type_obj is None:
+                vn_type_obj = VirtualNetworkType()
 
-    if 'external' in conf:
-        vn_obj.set_router_external(conf['external'])
+            if 'allow_transit' in vntype_conf:
+                allow_transit_attr = vn_type_obj.get_allow_transit()
+                if vntype_conf['allow_transit'] != allow_transit_attr:
+                    changes['allow_transit'] = \
+                        {'from': allow_transit_attr,
+                         'to': vntype_conf['allow_transit']}
+                    vn_type_obj.set_allow_transit(vntype_conf['allow_transit'])
+            if 'vxlan_network_identifier' in vntype_conf:
+                vxlan_net_id = vn_type_obj.get_vxlan_network_identifier()
+                if vntype_conf['vxlan_network_identifier'] != vxlan_net_id:
+                    vn_type_obj.set_vxlan_network_identifier(
+                        vntype_conf['vxlan_network_identifier'])
+                    changes['vxlan_network_identifier'] = \
+                        {'from': vxlan_net_id,
+                         'to': vntype_conf['vxlan_network_identifier']}
+            if 'forwarding_mode' in vntype_conf:
+                forwarding_mode_attr = vn_type_obj.get_forwarding_mode()
+                if vntype_conf['forwarding_mode'] in ['l2_l3', 'l2', 'l3'] and \
+                        vntype_conf['forwarding_mode'] != forwarding_mode_attr:
+                    changes['forwarding_mode'] = \
+                        {'from': forwarding_mode_attr,
+                         'to': vntype_conf['forwarding_mode']}
+                    vn_type_obj.set_forwarding_mode(
+                        vntype_conf['forwarding_mode'])
+            if 'mirror_destination' in vntype_conf:
+                mirror_dst_attr = vn_type_obj.get_mirror_destination()
+                if vntype_conf['mirror_destination'] != mirror_dst_attr:
+                    changes['mirror_destination'] = \
+                        {'from': mirror_dst_attr,
+                         'to': vntype_conf['mirror_destination']}
+                    vn_type_obj.set_mirror_destination(
+                        vntype_conf['mirror_destination'])
+            if 'rpf' in vntype_conf:
+                rpf_attr = vn_type_obj.get_rpf()
+                if vntype_conf['rpf'] != rpf_attr:
+                    changes['rpf'] = \
+                        {'from': rpf_attr,
+                         'to': vntype_conf['rpf']}
+                    vn_type_obj.set_rpf(vntype_conf['rpf'])
+            vn_obj.set_virtual_network_properties(vn_type_obj)
 
-    if 'allow_transit' in conf:
-        vn_type_obj.set_allow_transit(conf['allow_transit'])
-
-    if 'forwarding_mode' in conf:
-        if conf['forwarding_mode'] in ['l2_l3', 'l2', 'l3']:
-            vn_type_obj.set_forwarding_mode(conf['forwarding_mode'])
-
-    if 'rpf' in conf:
-        vn_type_obj.set_rpf(conf['rpf'])
-
-    if 'mirror_destination' in conf:
-        vn_type_obj.set_mirror_destination(conf['mirror_destination'])
-
-    vn_obj.set_virtual_network_properties(vn_type_obj)
-
-    # create virtual network
-    if __opts__['test']:
-        ret['result'] = None
-        ret['comment'] = ("Virtual network with name {0} will be created"
-                          .format(name))
+        if changes:
+            ret['changes'] = changes
+            if __opts__['test']:
+                ret['result'] = None
+                ret['comment'] = "Virtual network " + name + " will be updated"
+            else:
+                ret['comment'] = "VirtualNetwork " + name + " has been updated"
+                vnc_client.virtual_network_update(vn_obj)
+        else:
+            ret['comment'] = 'Virtual network ' + name + \
+                             ' already exists and is updated'
     else:
-        vnc_client.virtual_network_create(vn_obj)
-        # if network is external create floating ip pool
-        if 'external' in conf:
-            if conf['external']:
-                pool_name = 'default'
-                _create_floating_ip_pool(pool_name,
-                                         vn_obj,
-                                         **kwargs)
+        vn_obj = VirtualNetwork(name, prj_obj)
 
-        ret['comment'] = ("Virtual network with name {0} was created"
-                          .format(name))
+        # Configure IPAM properties
+        if subnet_conf:
+            if 'ip_prefix' in subnet_conf and 'ip_prefix_len' in subnet_conf:
+                vn_obj.add_network_ipam(ipam_obj,
+                                        VnSubnetsType(ipam_subnets=[ipam_sn]))
+
+        # Configure VirtualNetwork properties
+        if router_external is not None:
+            vn_obj.set_router_external(router_external)
+        if route_target_list is not None:
+            vn_obj.set_route_target_list(route_target_list)
+
+        # Configure VirtualNetworkType properties
+        if vntype_conf:
+            vn_type_obj = VirtualNetworkType()
+            if 'allow_transit' in vntype_conf:
+                vn_type_obj.set_allow_transit(vntype_conf['allow_transit'])
+            if 'vxlan_network_identifier' in vntype_conf:
+                vn_type_obj.set_vxlan_network_identifier(
+                    vntype_conf['vxlan_network_identifier'])
+            if 'forwarding_mode' in vntype_conf:
+                if vntype_conf['forwarding_mode'] in ['l2_l3', 'l2', 'l3']:
+                    vn_type_obj.set_forwarding_mode(
+                        vntype_conf['forwarding_mode'])
+            if 'mirror_destination' in vntype_conf:
+                vn_type_obj.set_mirror_destination(vntype_conf['mirror_destination'])
+            if 'rpf' in vntype_conf:
+                vn_type_obj.set_rpf(vntype_conf['rpf'])
+            vn_obj.set_virtual_network_properties(vn_type_obj)
+
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = "VirtualNetwork " + name + " will be created"
+        else:
+            vnc_client.virtual_network_create(vn_obj)
+            ret['comment'] = "VirtualNetwork " + name + " has been created"
+            ret['changes'] = {'VirtualNetwork': {'created': name}}
     return ret
 
 
diff --git a/_states/contrail.py b/_states/contrail.py
index 21a1d35..0d1a0bd 100644
--- a/_states/contrail.py
+++ b/_states/contrail.py
@@ -348,22 +348,6 @@
     virtual_network_create:
       contrail.virtual_network_present:
         - name: virtual_network_name
-        - conf:
-            domain: domain name
-            project: domain project
-            ipam_domain: ipam domain name
-            ipam_project: ipam project name
-            ipam_name: ipam name
-            ip_prefix: xxx.xxx.xxx.xxx
-            ip_prefix_len: 24
-            asn: 64512
-            target: 10000
-            external: False
-            allow_transit: False
-            forwading_mode: 'l2_l3'
-            rpf: 'disabled'
-            mirror_destination: False
-
 
 
 Enforce Floating Ip Pool configuration
@@ -834,17 +818,52 @@
     return ret
 
 
-def virtual_network_present(name, conf=None, **kwargs):
+def virtual_network_present(name, domain='default-domain', project='admin',
+                            ipam_domain='default-domain',
+                            ipam_project='default-project',
+                            ipam_name='default-network-ipam',
+                            router_external=None, route_target_list=None,
+                            subnet_conf=None, vntype_conf=None, **kwargs):
     '''
     Ensure that the virtual network exists.
 
-    :param name: Name of the virtual network
-    :param conf: Key:Value pairs used for network creation
+    :param name (str): name of the network (required)
+    :param domain (str): name of domain (optional)
+    :param project (str): name of project (optional)
+    :param ipam_domain (str): domain for ipam (optional)
+    :param ipam_project (str): project for ipam (optional)
+    :param ipam_name (str): name of IP Address Management (optional)
+    :param router_external (bool): When true, this virtual network is openstack
+        router external network. (optional)
+    :param route_target_list (list): route target list - format is
+        ['target:<asn>:<target>']
+    :param subnet_conf (dict):
+        ip_prefix (string) optional - format is xxx.xxx.xxx.xxx
+        ip_prefix_len (int) optional - format is xx
+    :param vntype_conf (dict):
+        allow_transit (boolean) optional - enable allow transit
+        vxlan_network_identifier (int) - VxLAN VNI value for network
+        forwarding_mode (any of ['l2_l3','l2','l3']) optional
+            - packet forwarding mode for this virtual network
+        rpf (any of ['enabled','disabled']) optional
+            - Enable or disable Reverse Path Forwarding check
+        for this network
+        mirror_destination (boolean) optional
+            - Mark the vn as mirror destination network
     '''
 
-    ret = __salt__['contrail.virtual_network_create'](name, conf, **kwargs)
+    ret = __salt__['contrail.virtual_network_create'](
+        name, domain='default-domain', project='admin',
+        ipam_domain='default-domain',
+        ipam_project='default-project',
+        ipam_name='default-network-ipam',
+        router_external=None, route_target_list=None,
+        subnet_conf=None, vntype_conf=None, **kwargs)
+    if len(ret['changes']) == 0:
+        pass
     return ret
 
+
 def floating_ip_pool_present(vn_name,
                              vn_project,
                              vn_domain=None,