flex.py #36

  • //
  • guest/
  • perforce_software/
  • p4flexclone/
  • demo/
  • flex.py
  • View
  • Commits
  • Open Download .zip Download (33 KB)
#!/usr/bin/env python

import os
import pwd
import sys
import subprocess

from P4 import P4, P4Exception

sys.path.append("/usr/local/lib/netapp-manageability-sdk-5.3.1/lib/python/NetApp")
from NaServer import *



if sys.version_info[0] >= 3:
    from configparser import ConfigParser
else:
    from ConfigParser import ConfigParser


# ---------------------------------------------------------------------------
# Singleton class (of sorts) for loading configuration
# ---------------------------------------------------------------------------
class Config:
    def __init__(self, file):
        self.config = ConfigParser()
        try:
            self.config.read(file)
        except:
            print("Could not read config file: %s" % file)
            sys.exit(2)
            
    def get(self, key):
        parts = key.split(".")
        try:
            value = self.config.get(parts[0], parts[1])
        except:
            print("Could not access config: %s" % key)
            sys.exit(2)
        return value

dir = os.path.dirname(os.path.realpath(__file__))
config = Config(dir + "/flex.cfg")
FLEX_SNAP = config.get("p4.snap")
FLEX_CLONE = config.get("p4.clone")
# ---------------------------------------------------------------------------



# ---------------------------------------------------------------------------
# NetApp Server connection as 'admin' user
# ---------------------------------------------------------------------------
class NaFlex:
    def __init__(self):
        self.server = config.get("NaServer.server")
        self.port = config.get("NaServer.port")
        self.user = config.get("NaServer.admin_user")
        self.passwd = config.get("NaServer.admin_passwd")
        self.vserver = config.get("NaServer.vserver")
        self.transport = config.get("NaServer.transport")
        self.style = config.get("NaServer.style")
        self.server_type = config.get("NaServer.server_type")
        self.aggr = config.get("NaServer.aggr")
        self.mount = config.get("NaServer.mount_base")

    # Sets the filer properties for connection
    # @returns the NaServer connection
    def get(self):
        s = NaServer(self.server, 1 , 15)
        s.set_server_type(self.server_type)
        resp = s.set_style(self.style)
        if (resp and resp.results_errno() != 0) :
            r = resp.results_reason()
            print ("Failed to set authentication style " + r + "\n")
            sys.exit (2)

        s.set_admin_user(self.user, self.passwd)
        resp = s.set_transport_type(self.transport)
        if (resp and resp.results_errno() != 0) :
            r = resp.results_reason()
            print ("Unable to set HTTP transport " + r + "\n")
            sys.exit (2)
            
        s.set_vserver(self.vserver)
        s.set_port(self.port)
	return s

    # Creates the volume based on name and size
    def create(self, name, size):
	api = NaElement("volume-create")
        api.child_add_string("containing-aggr-name", self.aggr)
        api.child_add_string("size", size)
        api.child_add_string("volume", name)
        # api.child_add_string("junction-active", "true")
        jpath = "/" + name
        api.child_add_string("junction-path", jpath)

        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

    # Takes a snapshot of specified volume
    def snapshot(self, volname, snapname):
	api = NaElement("snapshot-create")
        api.child_add_string("volume", volname)
        api.child_add_string("snapshot", snapname)

        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())
        
    # Creates a volume clone based on the snapshot of parent volume
    def clone(self, cvolname, psnapname, pvolname):
        api = NaElement("volume-clone-create")
        api.child_add_string("junction-active", "true")
        jpath = "/" + cvolname
        api.child_add_string("junction-path", jpath)
        api.child_add_string("parent-snapshot", psnapname)
        api.child_add_string("parent-volume", pvolname)
        api.child_add_string("volume", cvolname)
        
        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

    # Deletes the volume by unmounting, offlining and then deleting volume from vserver
    def delete(self, volname):

        # Check to make sure we own the volume to delete
#        st = os.stat(self.mount + "/" + volname)
#        user = self.call.getUser()
#        uid = pwd.getpwname(user).pw_uid
#        gid = pwd.getpwname(user).pw_gid
#        if (uid != st.st_uid):
#            raise NAException("You do not have permission to delete volume. Owner of volume is %s." % (pwd.getpwuid(st.st_uid).pw_name))

        # Check to make sure volume passed in is not vserver root or node volume
        # Root and node volumes are required to keep info on state of vserver and node
        api = NaElement("volume-get-iter")

        # Build the input values for the api query call
        xi1 = NaElement("query")
        api.child_add(xi1)
        xi2 = NaElement("volume-attributes")
        xi1.child_add(xi2)
        xi3 = NaElement("volume-id-attributes")
        xi2.child_add(xi3)
        xi3.child_add_string("name", volname)

        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

        vlist = xo.child_get("attributes-list")
        vsattrs = None
        if (vlist != None):
            vol = vlist.child_get("volume-attributes")
            vsattrs = vol.child_get("volume-state-attributes")

        if (vsattrs != None):
            isvroot = vsattrs.child_get_string("is-vserver-root")
            isnroot = vsattrs.child_get_string("is-node-root")
            if ((isvroot == "true") or (isnroot == "true")):
                raise NAException("Not authorized to delete vserver root-volume %s. Go directly to the NetApp Filer to conduct this operation" % (volname))

        # Unmount 
        api = NaElement("volume-unmount")
        api.child_add_string("force", "false")
        api.child_add_string("volume-name", volname)
        
        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

        # Offline
        api = NaElement("volume-offline")
        api.child_add_string("name", volname)
        
        xo = self.get().invoke_elem(api)
        if ((xo.results_status() == "failed") and (xo.results_errno() != "13042")):
            raise NAException(xo.sprintf())

        # Delete
        api = NaElement("volume-destroy")
        api.child_add_string("name", volname)

        xo = self.get().invoke_elem(api)
        if (xo.results_status() == "failed"):
            raise NAException(xo.sprintf())
    
    # Prints out the list of volumes 
    def vlist (self):
        list = []
        
        # Get First Set of mrecs (100) records
        mrecs = "100"
        api = NaElement("volume-get-iter")
        api.child_add_string("max-records", mrecs)
        xo = self.get().invoke_elem(api)

        if(xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

        # Set up to get next set of records
        tag = xo.child_get_string("next-tag")
  
        # Add vollist results to list and then Get Following set of records
        # Break from loop when the vollist is None or next-tag is None
        while True:
            vollist = xo.child_get("attributes-list")

            if (vollist == None): 
                break
            
            for vol in vollist.children_get():
                volattrs = vol.child_get("volume-id-attributes")
                volpname = ""
                
                volstateattrs = vol.child_get("volume-state-attributes")
                if (volstateattrs != None):
                    state = volstateattrs.child_get_string("state") 
                    isroot = volstateattrs.child_get_string("is-vserver-root")
                    
                volcattrs = vol.child_get("volume-clone-attributes")
                if (volcattrs != None): 
                    volpattrs = volcattrs.child_get("volume-clone-parent-attributes")
                    if (volpattrs != None):
                        volpname = volpattrs.child_get_string("name")

                # Only print out volume if volume is online, not a clone, and not vserver root volume
                if (state == "online") and (volpname == "") and (isroot != "true"):
                    list.append(volattrs);

            api = NaElement("volume-get-iter")
            api.child_add_string("max-records", mrecs)
            api.child_add_string("tag", tag)
            xo = self.get().invoke_elem(api)
            if(xo.results_status() == "failed"):
                raise NAException(xo.sprintf())

            # Get next tag which indicates if there are more records
            tag = xo.child_get_string("next-tag")
            if (tag == None):
                break
            
        return list

    # Print out list of snapshots based on volume name
    def slist(self, vname):

        # Get First Set of records
        api = NaElement("snapshot-list-info")
        api.child_add_string("volume", vname)

        xo = self.get().invoke_elem(api)

        if (xo.results_errno() != 0) :
            raise NAException(xo.sprintf())

        # # get snapshot list
        snapshotlist = xo.child_get("snapshots")
        print("\n")

        if ((snapshotlist != None) and (snapshotlist != "")) :
            # iterate through snapshot list
            snapshots = snapshotlist.children_get()
            for ss in snapshots:
                sname = ss.child_get_string("name")
                print("snapshot:%s" % (sname))
        else:
            print("No snapshots for volume " + vname)

    # Print out volume name and their snapshots
    def slistall(self):
        # Get first set of records defined by mrecs
        mrecs = "100"
        api = NaElement("snapshot-get-iter")
        api.child_add_string("max-records", mrecs)
        xo = self.get().invoke_elem(api)

        if(xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

        # Set up tag to get next set of records
        tag = xo.child_get_string("next-tag")
        
        # Need to go thru this loop at least once before checking for next tag
        # Break out of loop if snaplist is None or next-tag is None
        while True:
            # Get list of snapshot-info attributes
            snaplist = dict()
            snaplist = xo.child_get("attributes-list")

            if (snaplist == None):
                break

            # Go thru list and print out volume and snapshot name
            for snap in snaplist.children_get(): 
                print("snapshot: %s:%s" % (snap.child_get_string("volume"), snap.child_get_string("name")))
                
            # Get next set of records
            api = NaElement("snapshot-get-iter")
            api.child_add_string("max-records", mrecs)
            api.child_add_string("tag", tag)
            xo = self.get().invoke_elem(api)
            if(xo.results_status() == "failed"):
                raise NAException(xo.sprintf())

           # Tag indicates if there are more records
            tag = xo.child_get_string("next-tag")

            # Break out of loop.  Tag of None indicates no more records
            if (tag == None):
                break

    # # Prints out list of clones with their corresponding parent volume and parent snapshot
    def clist(self):
        # Get first set number of records defined by mrecs
        mrecs = "100"
        api = NaElement("volume-clone-get-iter")
        api.child_add_string("max-records", mrecs)
        xo = self.get().invoke_elem(api)

        if(xo.results_status() == "failed"):
            raise NAException(xo.sprintf())

        # Set up to get next set of records
        tag = xo.child_get_string("next-tag")
        print("\n")
        
        # Need to go thru this loop at least once before checking for next tag
        while True:
            # Get list of snapshot-info attributes
            clonelist = dict()
            clonelist = xo.child_get("attributes-list")

            if (clonelist == None):
                break

            for clone in clonelist.children_get(): 
                print("clone: %s:%s:%s" % (clone.child_get_string("parent-volume"), clone.child_get_string("parent-snapshot"), clone.child_get_string("volume")))
                
            # Get next set of records
            api = NaElement("volume-clone-get-iter")
            api.child_add_string("max-records", mrecs)
            api.child_add_string("tag", tag)
            xo = self.get().invoke_elem(api)
            if(xo.results_status() == "failed"):
                raise NAException(xo.sprintf())

           # Tag indicates if there are more records
            tag = xo.child_get_string("next-tag")

            # Break if no more records
            if (tag == None):
                break


class NAException(Exception):
    def __init__(self, e):
        self.error = e
# ---------------------------------------------------------------------------



# ---------------------------------------------------------------------------
# Perforce connection as 'flex' user
# ---------------------------------------------------------------------------
class P4Flex:
    def __init__(self):
        self.port = config.get("p4.port")
        self.user = config.get("p4.user")
        self.passwd = config.get("p4.passwd")
        
    def getP4(self):
        p4 = P4()
        p4.user = self.user
        p4.port = self.port
        p4.connect()
        p4.password = self.passwd
        p4.run_login()
        return p4
# ---------------------------------------------------------------------------
   
       

# ---------------------------------------------------------------------------
# Parse the Broker's request and provide access to the user's environment
# ---------------------------------------------------------------------------
class Broker:
    def __init__(self):
        self.args = {}
        # Comment out stdin and uncomment block below for debug.
        lines = sys.stdin.readlines()
        # lines = []
        # with open("in.txt") as f:
        #    lines = f.readlines()

        for line in lines:
            parts = line.split(": ")
            self.args[parts[0]] = parts[1].rstrip()  
    
    def getP4(self):
        p4 = P4()
        p4.user = self.args['user']
        p4.port = self.args['clientPort']
        p4.client = self.args['workspace']
        p4.connect()
        
        # Use flex account to login user
        s4 = P4Flex().getP4()
        s4.run_login(self.args['user'])
        return p4
    
    def getPort(self):
        return self.args['clientPort']
    
    def getUser(self):
        return self.args['user']
    
    def getClient(self):
        return self.args['workspace']
    
    def getCommand(self):
        if 'Arg0' in self.args:
            return self.args['Arg0']
        else:
            return None
    
    def getOptions(self):
        c = 1
        opts = []
        while True:
            key = 'Arg' + str(c)
            c += 1
            if key in self.args:
                opts.append(self.args[key])
            else:
                break
               
        join = "" 
        list = []
        for opt in opts:
            if join:
                opt = join + opt
                join = ""
            if opt.startswith('-') and len(opt) == 2:
                join = opt
            else:
                list.append(opt)
        if join:
            list.append(opt)
        return list
# ---------------------------------------------------------------------------

     

# ---------------------------------------------------------------------------
# Handle Broker request and invoke the registered 'flex' command
# ---------------------------------------------------------------------------
class Flex:
    def __init__(self):
    
        # Process Broker arguments
        self.call = Broker()
        self.command = self.call.getCommand()
        self.args = self.call.args
        self.opts = self.call.getOptions()
   
        # Build table for function pointers     
        self.cmdFn = {
            'volume': self.volume,
            'volumes': self.volumes,
            'snapshot': self.snapshot,
            'snapshots': self.snapshots,
            'clone': self.clone,
            'clones': self.clones,
            'help': self.help

            };
            
        if not self.isnfs(NaFlex().mount):
            print("action: REJECT")
            print("message: \"%s\"" % "NetApp nfs volume is not mounted.")
            return

        # Call command
        if self.command in self.cmdFn:
            self.cmdFn[self.command]()
        else:
            self.usage()     
    
    
    def volume(self):
        # Check for 'admin' permission
        if not self.permission():
            print("action: REJECT")
            print("message: \"%s\"" % "You don't have permission for this operation.")
            return
        
        # Get options (set defaults) 
        vname = ""
        size = "1G"
        for o in self.opts:
            if o.startswith('-d'):
                del_name = o[2:]
                self.vol_del(del_name)
                return
            if o.startswith('-s'):
                size = o[2:]
            else:
                vname = o
            
        if not vname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex volume name provided")
            return
        
        # NetApp connection
        netapp = NaFlex()
        
        # Create NetApp volume
        try:  
            netapp.create(vname, size)

            # Set file ownership of newly created volume.
            user = self.call.getUser()
            path = netapp.mount + "/" + vname
            self.chown(user, path)

            msg = vname + ". Mounted on " + path + "."
            print("action: RESPOND")
            print("message: \"Created flex volume %s\"" % msg) 
                  
        except NAException as e:
            print("action: RESPOND")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)
        except Exception as e:
            # If got unexpected error, delete the volume created
            error = "Unexpected Error: " + str(e)
            try:
                netapp.delete(vname)
            except NAException as e:
                error += '\nNetApp Error: ' + e.error
            print("action: RESPOND")
            print("message: \"%s\"" % error)


    def volumes(self):
        # NetApp connection
        netapp = NaFlex()

        # List the volumes
        try:  
            list = ""
            vols = netapp.vlist()
            for v in vols:
                list += "volume '"
                list += v.child_get_string("name") + "' "
                #list += v.child_get_string("name-ordinal") + " "
                #list += v.child_get_string("owning-vserver-name") + " "
                #list += "'" + v.child_get_string("comment")[:50] + "'"
                list += "\n"
                
            print("action: RESPOND")                  
            print("message: \"%s\"" % list)
            
        except NAException as e:
            print("action: RESPOND")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)

 
    # Builds a new FLEX_SNAP workspace to create the snapshot
    def snapshot(self):
        # Get options (set defaults)
        vname = ""
        sname = ""
        from_client_name = self.call.getClient()
        for o in self.opts:
            if o.startswith('-V'):
                vname = o[2:]
            if o.startswith('-c'):
                from_client_name = o[2:]
            else:
                sname = o
                
        if not vname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex volume name provided")
            return
        
        if not sname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex clone name provided")
            return
        
        if ':' in sname:
            print("action: REJECT")
            print("message: \"%s\"" % "Flex clone name must not use ':'")
            return     
        
        if not self.permission():
            print("action: REJECT")
            print("message: \"%s\"" % "You don't have permission for this operation.")
            return        
        
        # NetApp/Perforce connection as Flex
        netapp = NaFlex()
        p4 = P4Flex().getP4()
        
        # Create NetApp snapshot
        try:
            netapp.snapshot(vname, sname)
        
        except NAException as e:
            print("action: RESPOND")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)
              
        # Create Perforce workspace for snapshot
        try:  
            from_client = p4.fetch_client(from_client_name)
            root = from_client['Root']
            
            # Clone client workspace
            flex_client_name = FLEX_SNAP + vname + ':' + sname
            p4.client = flex_client_name
            flex_client = p4.fetch_client("-t", from_client_name, flex_client_name)
            
            # Set workspace options: root to mounted volume
            path = netapp.mount + "/" + vname
            flex_client['Root'] = path
            flex_client['Host'] = ""
            #flex_client['Options'] = flex_client['Options'].replace(" unlocked", " locked")
            p4.save_client(flex_client)
            
            # Populate have list
            p4.run_sync("-k", "//" + flex_client_name + "/...@" + from_client_name)
            
            print("action: RESPOND")
            print("message: \"Created flex snapshot %s\"" % sname)
            
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()

   
    def permission(self):
        try:
            # Perforce connection as Caller
            p4 = self.call.getP4()
            for p in p4.run_protects():
                if (p['perm'] == 'admin') or (p['perm'] == 'super'):
                    return True    
            return False
        
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()              


    def snapshots(self):
        try:  
            # Perforce connection as Caller
            p4 = self.call.getP4()
            snapshots = p4.run_clients("-e", FLEX_SNAP + "*")
            list = ""
            for v in snapshots:
                name = v['client'][len(FLEX_SNAP):]
                part = name.split(':', 1)
                list += "snapshot '" + part[1] + "' volume '" + part[0] + "' root '" + v['Root'] + "'\n"
            
            print("action: RESPOND")
            print("message: \"%s\"" % list)
            
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()
   
   
    # Clones a new FLEX_CLONE workspace from the parent flexSnap_
    def clone(self):
        # Get options 
        vname = ""
        sname = ""
        cname = ""
        for o in self.opts:
            if o.startswith('-d'):
                del_name = o[2:]
                self.clone_del(del_name)
                return
            if o.startswith('-V'):
                vname = o[2:]
            if o.startswith('-S'):
                sname = o[2:]
            else:
                cname = o
        
        if not vname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex volume name provided")
            return
        
        if not sname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex snapshot name provided")
            return
        
        if not cname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex clone name provided")
            return
        
        if ':' in cname:
            print("action: REJECT")
            print("message: \"%s\"" % "Flex clone name must not use ':'")
            return  
        
        # NetApp/Perforce connection as Caller
        netapp = NaFlex()
        p4 = self.call.getP4()
        path = netapp.mount + "/" + cname
        
        # Create NetApp clone from snapshot
        try:
            netapp.clone(cname, sname, vname)
        
        except NAException as e:
            print("action: RESPOND")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)
              
        try:  
            # Verify parent client exists
            parent_client_name = FLEX_SNAP + vname + ":" + sname
            list = p4.run_clients("-e", parent_client_name)
            if len(list) != 1:
                print("action: REJECT")
                print("message: \"Flex parent %s does not exist.\"" % parent_client_name)
                return
                        
            # Clone client workspace
            clone_client_name = FLEX_CLONE + cname
            p4.client = clone_client_name
            clone_client = p4.fetch_client("-t", parent_client_name, clone_client_name)
            clone_client['Root'] = path
            #clone_client['Options'] = clone_client['Options'].replace(" unlocked", " locked")
            p4.save_client(clone_client)
            
            # Generate P4CONFIG file
            self.p4config(path, clone_client_name)
        
            # Populate have list
            p4.run_sync("-k", "//" + clone_client_name + "/...@" + parent_client_name)
            
            # Set file ownership
            user = self.call.getUser()
            self.chown(user, path)
        
            msg = clone_client_name + ". Mounted on " + path + "."
            print("action: RESPOND")
            print("message: \"Created flex clone client %s\"" % msg) 
        
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()

    
    def chown(self, user, path):
        # Checks need to be done in case of failure
        uid = pwd.getpwnam(user).pw_uid
        gid = pwd.getpwnam(user).pw_gid
        os.system('sudo chown -R ' + str(uid) + ':' + str(gid) + ' ' + path)
            

    def isnfs(self, path):
        # Check if path is nfs mounted.
        fstype = subprocess.check_output('stat -f -L -c %T ' + path, shell=True)
        if (fstype == 'nfs\n'):
            return True
        return False

    def clones(self):
        # Get options
        all = False 
        for o in self.opts:
            if o == "-a":
                all = True
        
        try:  
            # Perforce connection as Caller
            p4 = self.call.getP4()
            clones = []
            if all:
                clones = p4.run_clients("-e", FLEX_CLONE + "*")
            else:
                user = self.call.getUser()
                clones = p4.run_clients("-e", FLEX_CLONE + "*", "-u", user)
            
            list = ""
            for c in clones:
                name = c['client'][len(FLEX_CLONE):]
                list += "clone '" + name + "' owner '" + c['Owner'] + "' root '" + c['Root'] + "'\n"
            
            print("action: RESPOND")
            print("message: \"%s\"" % list)
            
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()
            
            
    def clone_del(self, cname):               
        if not cname:
            print("action: REJECT")
            print("message: \"%s\"" % "No Flex clone name provided")
            return
        
        # NetApp/Perforce connection as Caller
        netapp = NaFlex()
        p4 = self.call.getP4()
        
        # Delete Perforce workspace
        try:  
            client_name = FLEX_CLONE + cname
            p4.run_client("-d", client_name)
            
            print("action: REJECT")
            print("message: \"Deleted Flex clone %s\"" % cname)
            
        except P4Exception:
            print("action: REJECT")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            return
            
        finally:
            p4.disconnect()
            
        # If client delete succeed, Delete NetApp clone
        try:
            netapp.delete(cname)
        
        except NAException as e:
            print("action: REJECT")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)
            return
        
        except Exception as e: 
            print("action: REJECT")
            error = '\nUnexpected error: ' + str(e)
            print("message: \"%s\"" % error)
            return
            
            
    def vol_del(self, vname):               
        if not vname:
            print("action: REJECT")
            print("message: \"%s\"" % "No flex volume name provided")
            return
        
        # NetApp/Perforce connection as Flex
        netapp = NaFlex()
        p4 = P4Flex().getP4()
        
        # Delete Perforce workspaces
        try:
            clones = p4.run_clients("-e", FLEX_SNAP + vname + "*")
            
            list = ""
            for c in clones:
                client_name = c['client']
                p4.run_client("-f", "-d", client_name)
                list += "   deleted client: %s\n" % client_name
            
            print("action: RESPOND")
            message = "Deleted Flex volume %s\n" % vname
            message += list
            print("message: \"%s\"" % message)
            
        except P4Exception:
            print("action: RESPOND")
            error = '\n'.join(p4.errors)
            error += '\n'.join(p4.warnings)
            print("message: \"%s\"" % error)
            
        finally:
            p4.disconnect()
            
        # If client delete succeed, Delete NetApp volume
        try:
            netapp.delete(vname)
        
        except NAException as e:
            print("action: REJECT")
            error = '\nNetApp Error: ' + e.error
            print("message: \"%s\"" % error)
            return
        
        except Exception as e: 
            print("action: REJECT")
            error = '\nUnexpected error: ' + str(e)
            print("message: \"%s\"" % error)
            return
    
    def p4config(self, path, client):
        p4config = os.getenv('P4CONFIG', '.p4config')
        p4port = self.call.getPort()
        p4user = self.call.getUser()
        
        fh = open(path + "/" + p4config, "w")
        fh.write("# Generated by p4 flex.\n");
        fh.write("P4PORT=" + p4port + "\n");
        fh.write("P4USER=" + p4user + "\n");
        fh.write("P4CLIENT=" + client + "\n");
        fh.close()
        
    def help(self):
        help = (
            "(FlexClone)\n\n"
            "    flex -- Perforce FlexClone operations\n\n"
            "    p4 flex volume -s size[M, G] name\n"
            "    p4 flex volume -d name\n"
            "    p4 flex volumes \n\n"
            "    p4 flex snapshot -V volume [-c client] name\n"
            "    p4 flex snapshots \n\n"
            "    p4 flex clone -V volume -S parent name\n"
            "    p4 flex clone -d name\n"
            "    p4 flex clones [-a]\n"
            "        \n"
            "        'p4 flex volume' will create a new volume.The '-d' flag will\n"
            "        delete the volume and associated snapshots.\n"
            "        \n"
            "        'p4 flex volumes' will display a list of all volumes.\n"
            "        \n"
            "        'p4 flex snapshot' will create a new snapshot.\n"
            "        \n"
            "        'p4 flex snapshots' will display a list of all flex snapshots.\n"
            "        \n"
            "        'p4 flex clone' will create a new flex clone. The '-d' flag will\n"
            "        delete the flex clone and associated client.\n"
            "        \n"
            "        'p4 flex clones' will display a list of all flex clones owned by that\n"
            "        user. The '-a' flag will list all flex clones globally.\n"
        )
        print("action: RESPOND")
        print("message: \"%s\"" % help)
        
    def usage(self):
        usage = (
            "Usage: flex { volume(s) | snapshot(s) | clone(s) }\n"
            "Missing/wrong number of arguments."
        )
        print("action: RESPOND")
        print("message: \"%s\"" % usage)
# ---------------------------------------------------------------------------



# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------
if __name__ == '__main__':
    flex = Flex()

    
    
    

# Change User Description Committed
#36 15417 Paul Allen Remove client lock.
#35 15070 Paul Allen spelling
#34 15068 Paul Allen Allow locking on snapshot workspaces and use '-f' on volume delete.
#33 15067 Paul Allen Disable workspace locking for demo.

Better solution is for user 'flex' to delete with a '-f', but check ownership first.
#32 15066 Paul Allen Update create clone message.
#31 15065 Paul Allen Back out changelist 15064
#30 15064 Paul Allen chown fix?
#29 15063 Paul Allen Remove description from 'flex volumes' output.
#28 15062 Paul Allen Use Flex user to login current user (needs super)
Minor fix to help
#27 15027 agnesj Added a check if NetApp volume is mounted, removed test function, and modified -P to -S for flex clone command.
#26 14926 Paul Allen Move chown operations to method and use system call to 'chown -R'
#25 14925 Paul Allen Generate a P4CONFIG file in the mounted clone's volume.
#24 14924 Paul Allen Set workspace 'Root' to mount location for clone and snapshot.
#23 14923 Paul Allen Show Owner when listing clones and volume when listing snapshots.

Includes a tidy up on all listing commands.
#22 14921 Paul Allen #review-14917

Verify delete permission for volume/clone. 

 - Only a Perforce 'admin' can delete a volume
 - Only a Perforce 'admin' and the clone's owner can delete a clone.

Use Perforce client workspace based permissions to enforce rules.  If the client delete succeeds assume all is ok to delete the volume/clone.

Example error:

bob$ p4 flex clone -d clone_2_1_a
Locked client 'flexClone:clone_2_1_a' owned by 'pallen'; use -f to force update.
#21 14895 agnesj Modified volume function such that when creating a volume the owner of volume is modified.
Also modified vol_del and clone_del functions to check for ownership of volume.  Only owners of volumes are allowed to delete.
#20 14881 agnesj Added checks in chown and volume functions.
And added location of newly created volume in message.
#19 14180 Paul Allen Set file ownership for clone's mounted files.

 + Minor fix to clone delete
#18 14179 Paul Allen Add support for 'flex volumes' command.
#17 14178 agnesj Modified NetApp delete to check if volume is a root volume (vserver or node) and to not allow p4 user to delete.
#16 14141 agnesj Added definition functions for volume list, snapshot list based on volume name, all snapshot list, clone list
#15 13997 Paul Allen Stop on error (don't delete clients if vol delete fails).
#14 13996 Paul Allen Added -s to volume for size.
#13 13993 Paul Allen Added NetApp commands to Flex.

Example:

4007::ws$ p4 flex volume vol1
Created flex volume vol1

4007::ws$ p4 flex snapshot -V vol1 snapshot1
Created flex snapshot snapshot1

4007::ws$ p4 flex snapshot -V vol1 snapshot2
Created flex snapshot snapshot2

4007::ws$ p4 flex snapshots
snapshot vol1:snapshot1 root /home/pallen/flexclone/demo/ws
snapshot vol1:snapshot2 root /home/pallen/flexclone/demo/ws

4007::ws$ p4 flex clone -V vol1 -P snapshot1 clone_A
Created flex clone client flexClone:clone_A

4007::ws$ p4 flex clone -V vol1 -P snapshot1 clone_B
Created flex clone client flexClone:clone_B

4007::ws$ p4 flex clones
Clone clone_A root /home/pallen/flexclone/demo
Clone clone_B root /home/pallen/flexclone/demo

4007::ws$ p4 clients
Client flexClone:clone_A 2015/06/17 root /home/pallen/flexclone/demo 'Created by pallen. '
Client flexClone:clone_B 2015/06/17 root /home/pallen/flexclone/demo 'Created by pallen. '
Client flexSnap:vol1:snapshot1 2015/06/17 root /home/pallen/flexclone/demo/ws 'Created by flex. '
Client flexSnap:vol1:snapshot2 2015/06/17 root /home/pallen/flexclone/demo/ws 'Created by flex. '
Client pallen-ws 2015/06/10 root /home/pallen/flexclone/demo/ws 'Created by pallen. '

4007::ws$ p4 flex clone -d clone_B
Deleted Flex clone clone_B

4007::ws$ p4 flex clone -d clone_A
Deleted Flex clone clone_A

4007::ws$ p4 flex volume -d vol1
Deleted Flex volume vol1
   deleted client: flexSnap:vol1:snapshot1
   deleted client: flexSnap:vol1:snapshot2

4007::ws$ p4 clients
Client pallen-ws 2015/06/10 root /home/pallen/flexclone/demo/ws 'Created by pallen. '
#12 13988 Paul Allen Tidy up formatting and imports.
#11 13985 agnesj Added definitions: clone, snapshot, and delete
Modified test functions to test each of the added functions
#10 13867 agnesj Made corrections to NaFlex Class to pull info from flex.cfg file
#9 13859 Paul Allen Config details for cloud demo + NaServer test
#8 13796 Paul Allen Moved options to a configuration file.

 - The config file 'flex.cfg' MUST be located in the same dir as the script
 - Provided a sample 'in.txt' file to simulate a broker call (used for debug only)
#7 13791 Paul Allen Changed 'volume' to 'snapshot' and minor fix to for auth/login.
#6 13702 Paul Allen Added Permission check for p4 flex volume command.
#5 13671 Paul Allen Added '-a' option to flex clones command.

Tidy up and hide flexVol/flexClone prefix from user.
#4 13669 Paul Allen Added '-d' option to delete flex clone client.
#3 13668 Paul Allen Added Perforce functionality for volume(s) and clone(s) commands.
#2 13628 Paul Allen set executable
#1 13627 Paul Allen New Python script with Implemented help/usage