Business Requirement
I was working on setting up an OCVS environment for one of the biggest stock exchanges in the APAC region. The customer’s requirement was to join the VC deployed by OCVS to a custom stock exchange domain privately hosted in the customer’s on-premises environment. I thought the process was simple.
- Create a listener and forwarder in OCI DNS.
- Create a rule to forward the DNS traffic for your custom domain (fluffyclouds.com in my case ) to the private DNS server.
- Create the OCI DNS Zones hosting OCVS components as conditional forwarders in my private MS AD.
- Test DNS resolution is working, follow the standard VC URL rename and domain join process.
Challenge
Well the process looks straightforward and there is a formal Oracle Guide to do this but it can be challenging to follow in a complex environment where the customer has a number of DNS zones. Finding the correct DNS zones for the OCVS components manually, was like finding something to watch on Netflix!
A lot of our customers are new to Oracle Cloud and OCVS, it would be unfair for us to assume that the customers will be able to easily find the private zones esp if you have a lot of them.
Solution
After I spent an all-nighter gathering the required information to create these conditional forwarders, I thought there could be a better way of doing this. That’s where I decided to use my new love Python to solve this problem.
In this article, I am using the naming conventions
Private Microsoft DNS Server IP | 192.168.100.11 |
Private Microsoft DNS Server Domain Name | fluffyclouds.com |
OCI Listener FQDN | ocvslistner.subnet10261946.ocvs1.oraclevcn.com |
OCI Listener IP | 10.1.254.211 |
OCI Forwarder FQDN | ocvsfwdr.subnet10261946.ocvs1.oraclevcn.com |
OCI Forwarder IP | 10.1.254.41 |
DNS for OCVS | Default OCI DNS |
For keeping the things simple, we will manually create the following:
- OCI Listener
- OCI Forwarder
- Rule to forward any fluffyclouds.com traffic to my private DNS server 192.169.100.11
We can also use the Python SDK to do this, but I already had an existing DNS FWD/RCVR so I am skipping that part.
Listener and Forwarder Created

DNS Foward Rule Created for fluffyclouds.com

Solution Automation
Now comes the fun part of the solution!
I decided to start with simple user inputs as shown below. “vcn-compartment-id” will be blank if the VCN is deployed in the same compartment as OCVS and that’s all the user needs to provide, automation will take care of the rest!
{ "ocvs-compartment-id": "ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxx", "vcn-compartment-id": "", "vcn-name":"yyyyyyyyy" }
Automation workflow
From the user inputs provided on a high level following tasks will be executed
- Use Python SDK to query the SDDC API
- Gather the SDDC details and push them to a JSON file.
- Gather ESXi details and push them to JSON.
- Get the VCN ID with the VCN name provided by the user.
- Find the default DNS resolver for the VCN.
- Get all the private OCI zones in the resolver.
- Iterate through each zone record to find a match with the SDDC elements.
- Push the matches to the JSON along with the reverse lookup zone information for ESXi and DNS Resolver / Listener.
- Write the JSON file to the local OS.
Here’s the process flow for the solution.

This is the script that does that magic for you!
I have added the running comments to explain the code.
import oci import json from datetime import datetime start = datetime.now() config = oci.config.from_file("~/.oci/configp", "DEFAULT") # open the JSON file f = open('/Users/barjindersingh/.oci/dnsinputs/userInputs.json') # parse the JSON file try: data = json.load(f) except json.decoder.JSONDecodeError: print("Invalid documentTemplate JSON") # User Input compartment_ocvs = data["ocvs-compartment-id"] if (data["vcn-compartment-id"]): compartment_vcn = data["vcn-compartment-id"] else: compartment_vcn = compartment_ocvs vcnName = data["vcn-name"] # Global Variables processInputs = {} selected_resolver_id = "" viewId = "" esxiName = "" sddc_id = "" ################ GET VCN ID with Name Start #################### # Initialize service client with default config file core_client = oci.core.VirtualNetworkClient(config) list_vcns_response = core_client.list_vcns( compartment_id=compartment_ocvs, limit=473, display_name= vcnName ) # Get the data from response vcnId = list_vcns_response.data[0].id ################ GET VCN ID with Name End #################### #################### SDDC Section Begin ########################### # Initialize service client with default config file ocvp_client = oci.ocvp.SddcClient(config) # Send the request to service, some parameters are not required, see API # doc for more info list_sddcs_response = ocvp_client.list_sddcs( compartment_id=compartment_ocvs, sort_order="DESC", sort_by="displayName" ) # Add the SDDC details to a JSON file. for item in list_sddcs_response.data.items: processInputs[str(item.nsx_manager_fqdn).split("-")[0]] = item.nsx_manager_fqdn processInputs[str(item.vcenter_fqdn).split("-")[0]] = item.vcenter_fqdn processInputs[str(item.hcx_fqdn).split("-")[0]] = item.hcx_fqdn sddc_id= item.id #################### SDDC Section End ########################### #################### ESXi Section Begin ########################### # Initialize service client with default config file ocvp_client = oci.ocvp.EsxiHostClient(config) # Send the request to ESXi service list_esxi_hosts_response = ocvp_client.list_esxi_hosts( sddc_id=sddc_id) # Get the data from response # Loop through the ESXi hosts and the display names to the JSON File for item in list_esxi_hosts_response.data.items: processInputs["ESXi-"+str(item.display_name).split("-")[1]] = item.display_name #################### ESXi Section END ########################### ##################### DNS Section################################################## # Create DNS Client dns_client = oci.dns.DnsClient(config) # Get a list of all the resolvers. list_resolvers_response = dns_client.list_resolvers( compartment_id=compartment_vcn, limit=37, sort_order="DESC", sort_by="displayName", lifecycle_state="ACTIVE", scope="PRIVATE") # Get the default view id for the listed VCN for x in list_resolvers_response.data: if (x.attached_vcn_id == vcnId): selected_resolver_id = x.id viewId = x.default_view_id # Get Default VCN Resolver get_resolver_response = dns_client.get_resolver( resolver_id=selected_resolver_id, scope="PRIVATE") # Update the JSON file with forwarders and resolvers for endpoint in get_resolver_response.data.endpoints: if(endpoint.is_forwarding): processInputs["DNSForwarder-" + str(endpoint.forwarding_address).split(".")[3]] = endpoint.name elif(endpoint.is_listening): processInputs["DNSListner-" + str(endpoint.listening_address).split(".")[3]] = endpoint.name # Get all the DNS Zones with the view ID all_zones = oci.pagination.list_call_get_all_results( dns_client.list_zones, compartment_vcn, scope="PRIVATE", view_id=viewId) #Loop through the zones and zone records to find a match and update the JSON file try: for k, v in processInputs.items(): for y in all_zones.data: zoneRecord = dns_client.get_zone_records(y.id) for b in zoneRecord.data.items: if str(b.rdata).split(".")[0].lower() == str(v).lower(): processInputs[k]["reverse"] = { "fqdn": b.domain, "zone": y.name, "rdata": b.rdata, "id": y.id} elif str(b.domain).lower() == str(v).lower() or str(b.domain).split(".")[0].lower() == str(v).lower(): processInputs[k] = {"fqdn": b.domain, "zone": y.name, "rdata": b.rdata, "id": y.id} except: print("OOps! An exception occurred finding the DNZ Zone Information") #Print the JSON File. print(json.dumps(processInputs, indent=4)) # Serializing json json_object = json.dumps(processInputs, indent = 4) # Writing to sample.json with open("OCVS-DNS-Zones.json", "w") as outfile: outfile.write(json_object) #calculate the execution time end = datetime.now() print("The time of execution of above program is :", str(end-start)[5:])
Below is the JSON output of the script. As you can see it lists the FQDN, Zone, Rdata, ID, and Reverse Zones for the SDDC components from the OCI DNS Private Zones. These inputs would be required to configure in the private MS DNS server.
{ "nsx": { "fqdn": "nsx-ocvs1.sddc.nrt.oci.oraclecloud.com", "zone": "sddc.nrt.oci.oraclecloud.com", "rdata": "10.1.3.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxxxx" }, "vcenter": { "fqdn": "vcenter-ocvs1.sddc.nrt.oci.oraclecloud.com", "zone": "sddc.nrt.oci.oraclecloud.com", "rdata": "10.1.3.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxxx" }, "hcxmgr": { "fqdn": "hcxmgr-ocvs1.sddc.nrt.oci.oraclecloud.com", "zone": "sddc.nrt.oci.oraclecloud.com", "rdata": "10.1.3.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxxxx" }, "ESXi-3": { "fqdn": "ocvs1-3.sub09090413461.ocvs1.oraclevcn.com", "zone": "sub09090413461.ocvs1.oraclevcn.com", "rdata": "10.1.0.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxxxx", "reverse": { "fqdn": "xx.0.1.10.in-addr.arpa", "zone": "1.10.in-addr.arpa", "rdata": "ocvs1-3.sub09090413461.ocvs1.oraclevcn.com.", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx" } }, "ESXi-2": { "fqdn": "ocvs1-2.sub09090413461.ocvs1.oraclevcn.com", "zone": "sub09090413461.ocvs1.oraclevcn.com", "rdata": "10.1.0.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxxx", "reverse": { "fqdn": "xx.0.1.10.in-addr.arpa", "zone": "1.10.in-addr.arpa", "rdata": "ocvs1-2.sub09090413461.ocvs1.oraclevcn.com.", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx" } }, "ESXi-1": { "fqdn": "ocvs1-1.sub09090413461.ocvs1.oraclevcn.com", "zone": "sub09090413461.ocvs1.oraclevcn.com", "rdata": "10.1.0.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx", "reverse": { "fqdn": "xx.0.1.10.in-addr.arpa", "zone": "1.10.in-addr.arpa", "rdata": "ocvs1-1.sub09090413461.ocvs1.oraclevcn.com.", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx" } }, "DNSForwarder-41": { "fqdn": "ocvsfwdr.subnet10261946.ocvs1.oraclevcn.com", "zone": "subnet10261946.ocvs1.oraclevcn.com", "rdata": "10.1.254.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx", "reverse": { "fqdn": "xx.254.1.10.in-addr.arpa", "zone": "1.10.in-addr.arpa", "rdata": "ocvsfwdr.subnet10261946.ocvs1.oraclevcn.com.", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.axxxx" } }, "DNSListner-211": { "fqdn": "ocvslistner.subnet10261946.ocvs1.oraclevcn.com", "zone": "subnet10261946.ocvs1.oraclevcn.com", "rdata": "10.1.254.xx", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.aaaaaaaapvivuqj6dcey75gsj65gfh33mqiv4bvlkfhznzpa7ag63rpemw7a", "reverse": { "fqdn": "xx.254.1.10.in-addr.arpa", "zone": "1.10.in-addr.arpa", "rdata": "ocvslistner.subnet10261946.ocvs1.oraclevcn.com.", "id": "ocid1.dns-zone.oc1.ap-tokyo-1.xxxx" } } }
The reason for using JSON as an output format is that it’s portable. I can use the python pypsrp module to configure these DNS zones with automation and use the above JSON as an input file.
Microsoft DNS Configuration
Now that, all the pre-requisites are in a single JSON file, the next steps are easy peasy!
Create a Reverse Lookup Zone aligned with the Oracle Cloud Infrastructure (OCI) Listener and create a pointer record for the Listener IP.

Create the conditional forwarders for the SDDC Components, ESXi Servers, and Reverse Zones for ESXi. All the entries should be pointing to the OCVS listener FQDN and IP Address created in the article above.

Each conditional forwarder configuration.

After the DNS zones are created, I am able to resolve the ESXi hosts, NSX manager and all other components from my fluffyclouds.com domain.
C:\Users\Administrator>ping -a 10.1.0.xx Pinging ocvs1-1.sub09090413461.ocvs1.oraclevcn.com [10.1.0.xx] with 32 bytes of data: Reply from 10.1.0.xx: bytes=32 time<1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time=1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time<1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time<1ms TTL=62 C:\Users\Administrator>ping ocvs1-2.sub09090413461.ocvs1.oraclevcn.com Pinging ocvs1-2.sub09090413461.ocvs1.oraclevcn.com [10.1.0.xx] with 32 bytes of data: Reply from 10.1.0.xx: bytes=32 time<1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time<1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time=1ms TTL=62 Reply from 10.1.0.xx: bytes=32 time=1ms TTL=62 C:\Users\Administrator>ping ocvsfwdr.subnet10261946.ocvs1.oraclevcn.com Pinging ocvsfwdr.subnet10261946.ocvs1.oraclevcn.com [10.1.254.xx] with 32 bytes of data: C:\Users\Administrator>ping nsx-ocvs1.sddc.nrt.oci.oraclecloud.com Pinging nsx-ocvs1.sddc.nrt.oci.oraclecloud.com [10.1.3.xxx] with 32 bytes of data: Reply from 10.1.3.xxx: bytes=32 time<1ms TTL=62 Reply from 10.1.3.xxx: bytes=32 time<1ms TTL=62 Reply from 10.1.3.xxx: bytes=32 time<1ms TTL=62 Reply from 10.1.3.xxx: bytes=32 time<1ms TTL=62
Conclusion
Oracle Cloud is a full-blown cloud platform with a range of services for every organisation large or small and Python SDK for OCI is a powerful tool to automate the cloud. There is a lot that can be automated with easy SDK options. I hope this article will help you save some time and effort. Keep reading!
Hi barji,
Very nice article i can relate this problem.
Regards,
Mayur