Updating MX VLAN Ports through API

SOLVED
Crash_Override
Here to help

Updating MX VLAN Ports through API

I am currently working on a function to modify MX ports for our retail deployments to make everything quicker and easier for me. I primarily am using the Meraki Python module, but for this particular API endpoint, that is not yet supported from what I can see by the Python module, so I am using requests to make the call directly. 

Now I have done this for a couple other functions with no major issue so far... but for some reason I am getting 400 from the response when I am using multiple parameters. When I use a single parameter it goes through absolutely fine. I am not a developer by trade so I do not have a lot of experience in this sort of thing, but using the Curl to Python converter here It converts the sample request from the documentation to the Python equivalent request of:

 

 

 

 

 

 

headers = {
    'X-Cisco-Meraki-API-Key': '<key>',
    'Content-Type': 'application/json',
}

data = '{"enabled":true,"type":"access","dropUntaggedTraffic":false,"vlan":3,"accessPolicy":"open"}'

response = requests.put('<a href="https://api.meraki.com/api/v0/networks/%7BnetworkId%7D/appliancePorts/%7BappliancePortId%7D" target="_blank">https://api.meraki.com/api/v0/networks/%7BnetworkId%7D/appliancePorts/%7BappliancePortId%7D</a>', headers=headers, data=data)

 


Using .format() on the URL string I replace the garbage for the network id and the MX port number with correct values. I modified the parameters to use the ones I care about. However, it seems if there is more than one parameter it is a bad request, but a single parameter is successful. You can see this from the unused ports (5-12) which only have the enabled:false parameter to disable them. Not sure what I am doing wrong, as I am doing everything the same as I have done before for other endpoints as far as the data being passed is concerned... what am I missing?

 

 

 

 

def update_mx_ports(HEADERS, NID):
    r = requests.get(
        "<a href="https://api.meraki.com/api/v0/networks/{}/appliancePorts".format(NID" target="_blank">https://api.meraki.com/api/v0/networks/{}/appliancePorts".format(NID</a>),
        headers=HEADERS,
    )

    if r.status_code == 200:
        print("Successfully retrieved MX LAN port info...")
        r = json.loads(r.text)
        for port in r:
            for k, v in port.items():
                if k == "number" and v == 3:
                    print("Entering Port 3")
                    data = '{"enabled":true,"type":"trunk","dropUntaggedTraffic":true,"allowedVlans":252,900,999}'
                    response = requests.put(
                        "<a href="https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format" target="_blank">https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format</a>(
                            NID, v
                        ),
                        headers=HEADERS,
                        data=data,
                    )
                    print(response)

                elif k == "number" and v == 4:
                    print("Entering Port 4")
                    data = '{"enabled":true,"type":"trunk","dropUntaggedTraffic":true,"allowedVlans":252,900,999}'
                    response = requests.put(
                        "<a href="https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format" target="_blank">https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format</a>(
                            NID, v
                        ),
                        headers=HEADERS,
                        data=data,
                    )
                    print(response)

                elif k == "number" and v > 4:
                    print("Entering Port", v)
                    data = '{"enabled":false}'
                    response = requests.put(
                        "<a href="https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format" target="_blank">https://api.meraki.com/api/v0/networks/{}/appliancePorts/{}".format</a>(
                            NID, v
                        ),
                        headers=HEADERS,
                        data=data,
                    )
                    print(response)

 




1 ACCEPTED SOLUTION

OK, after some testing, I found that I am wrong about using the Python list for the vlans. They want a string with comma separated values. Your data needs to look like this:

 

{"enabled": true, "type": "trunk", "dropUntaggedTraffic": true, "allowedVlans": "1,128"}

 

It also will not let you just use any values for the vlans. They have to already exist for the MX. On the device I was using for testing vlans 1 and 128 were already defined, so updating a port to use one, both, or 'any' worked fine. If I tried to assign "10" as a vlan, it would give me an error 400.

View solution in original post

14 REPLIES 14
jdsilva
Kind of a big deal

Oh wow... I mean no offence, but throw away that curl to python thingy. That is horrendous code to read!

 

**EDIT** I take that back. Your post changed. It's WAY more readable now. **

 

I would love to help you here, but it'll have to wait till later today. Honestly, I wouldn't try fixing this code, instead I'd help you write new code that's pure python instead of this hybrid stuff the converter has given you. 

 

If no one hasn't helped by the time I get home later I'll circle back here and help out. In the meantime, here's a bit of a shameless plug with code examples for creating VLANs. Not exactly what you're trying to do, but it might help you get started.

 

http://blog.brokennetwork.ca/2019/06/meraki-action-batches-in-action.html

 

Maybe look at just the first non-action batches example in that post and ignore the second one for now. 

CBurkhead
Building a reputation

The thing that jumps out at me immediately is the HTML tags in the requests statements. You should not need those at all. The requests.get also seems to have more info than is needed. It should be more like:

 

r = requests.get("https://api.meraki.com/api/v0/networks/{}/appliancePorts".format(NID),headers=HEADERS)

 

The same will be true for the requests.put statements.

 

I will need to look at the coding logic much more to determine if there are problem there as well, but from everything I have seen and done myself, the requests calls are totally wrong.

 

the html tags arent in my code.... idk where the hell they came from.... but when i added the code snippet they were automatically added, i assume because of the URL?

Capture.PNG

its not from the curl converter either, thats just taking the curl command and formatting it as a requests request:



Capture.PNG

Also I even tried to edit the code snippets after the fact posting in my code from notepad..... still keeps adding those html tags for some reason and making it unbearable..... hopefully the snipping tool capture of the function is more helpful. not sure what the frak the code snippet thing is doing ha

I think I may have it. The documentation of PUT/networks/{networkId}/appliancePorts/{appliancePortId} says that the allowedVlans needs to be "Comma-delimited list of the VLAN ID's allowed on the port, or 'all' to permit all VLAN's on the port."


I am going to assume that for Python this means that they want an actual Python list, which would mean that your data should look like:

data = '{"enabled":true,"type":"trunk","dropUntaggedTraffic":true,"allowedVlans":[252,900,999]}'

I actually didnt even think of that when I read that as so far its always been a string... I went and added a list as you noted, and also tried it using the string format method. However, now its producing a keyError for "enabled"...  

OK, after some testing, I found that I am wrong about using the Python list for the vlans. They want a string with comma separated values. Your data needs to look like this:

 

{"enabled": true, "type": "trunk", "dropUntaggedTraffic": true, "allowedVlans": "1,128"}

 

It also will not let you just use any values for the vlans. They have to already exist for the MX. On the device I was using for testing vlans 1 and 128 were already defined, so updating a port to use one, both, or 'any' worked fine. If I tried to assign "10" as a vlan, it would give me an error 400.

Nash
Kind of a big deal

You know, you could try to POST createNetworkVlan then if that returns anything other than 200, you can tell your code to then run a PUT updateNetworkVlan.

 

Handles the exception so you don't have to fuss about it. It'll either create or update the vlan as necessary.

 

I can throw together code later to kinda demonstrate this.

 

CBurkhead
Building a reputation

Agreed. If the vlan doesn't exist on the device, you will need to call createNetworkVlan. You can decide how you want to handle the error catching, try and create the vlan and if there is an error, update or try and update and if there is an error, create it.

 

How you do that will depend on your environment and if you expect to need to do more updating or creating for your devices.

Your info here actually did it. I didnt have " " around my comma seperated vlan id's. When I ran it with the double quotes it worked perfectly!

I am glad that you were able to solve the problem! I had a feeling that the double quotes around the vlans would help.

 

I have found that when I am not sure about the format of the data based on the endpoint documentation, I use the GET for the information I want to modify and then look at the data in my debugger, or at least print it out. If I need a particular scenario, I will set it up on a device and then pull the data and look at it. This is how I saw that the vlans had to be a string. 

 

In your situation, looking at the data from the GET might have given you the insight you needed, if you already had an MX that had multiple vlans defined on a port, and not just 'all'. 

 

For debugging things like this, print() and the debugger in an IDE are your friends.

I am trying to do the same thing but only updating VLAN subnet for 1000+ networks. Can you please share your code?I am having a really hard time to create it
Nash
Kind of a big deal

Can I suggest breaking down your blob into something more granular? It makes it easier to troubleshoot and read. I typically create a function specifically for the API calls that I'm trying to use. You can see an example of how I do that if you look at a script I wrote for copying admins from one org to another.

Get notified when there are additional replies to this discussion.