#!/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 | |
---|---|---|---|---|---|
#9 | 24146 | Paul Allen | Copy changes from punithc/p4flex | ||
#8 | 20227 | netapp | small change in list_flexclones function to show the flexclone path in Linux, versus just the junction path. | ||
#7 | 20171 | netapp | flex.py has new code that uses volume footprints instead of old cDOT8 code when printing out the list of clones. | ||
#6 | 20103 | netapp |
Latest code - Aug 15, 2016. Changes made to address : 1. Added in functionality for snapshot delete ('snapshot -V volname -d snapshotsname') 2. Corrected volume delete to not allow for Helix database volume delete, unless all clones are first removed 3. 'p4 flex clones' now produces full table showing Parent Volume, Parent-Snapshot, FlexClone name, Parent volume space consumed, FlexClone volume space consumed, FlexClone acutal (space versus parent volume), and the Clone-path 4. Flex Clones now thinly provisioned to allow for storage space savings 5. Code change to process P4 first when taking snapshots, and then follow with NetApp step versus previous reverse order |
||
#5 | 19802 | netapp | Corrected empty clone list error. | ||
#4 | 19800 | netapp |
Many changes - 1 ) Snapshot Delete Capability 2) Sizing of FlexClones versus original volume reported showing space saved 3) Check for volume Clones before volume deletion 4) Output of chown command when volumes created by user |
||
#3 | 19636 | netapp | Reverting back to Agnes' original | ||
#2 | 19474 | netapp |
Uploaded new files for project. Enhancements include: 1. Added in snapshot deletion capability 2. Created separate user clone mount directory, which can be different from the base directory 3. Cleaned up error handling for all p4 flex calls, so errors are handled with understandable output, versus program crashes 4. Added in ability to specify volume owner when creating a volume with �p4 flex volume �s �u�. |
||
#1 | 18997 | netapp |
Rename/move file(s) Project name change: moving //guest/netapp/p4flexclone/main/… to //guest/netapp/p4flex/main/… |
||
//guest/netapp/p4flexclone/main/demo/flex.py | |||||
#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 |