class ClusterSession: def __init__(self, cluster_ip, user, password, vserver=None): self.server = NaServer(cluster_ip, 1, 100) self.server.set_server_type('FILER') self.server.set_transport_type('HTTP') self.server.set_port(80) self.server.set_style('LOGIN') self.server.set_admin_user(user, password) if vserver is not None: self.server.set_vserver(vserver) def get_vserver(self): return self.server.get_vserver() def run_command(self, api): return self.server.invoke_elem(api) def get_nodes(self): api_call = NaElement('cluster-node-get-iter') output = self.run_command(api_call) if output.results_status() == 'failed': return output.results_status(), output.sprintf() else: cluster_node_info = output.children_get() for cni in cluster_node_info: if cni.has_children() == 1: nodes = cni.children_get() nodes_list = [] for n in nodes: nodes_list.append(n.child_get_string('node-name')) return nodes_list
class Filer: """A NetApp filer.""" def __init__(self, hostname, user, passwd): self.api = NaServer(hostname, 1, 3) self.api.set_style('LOGIN') self.api.set_admin_user(user, passwd) self.api.set_transport_type('HTTPS') self.name = hostname out = self.invoke('system-get-version') self.version = out.child_get_string('version') # Used for caching performance object descriptions: self.perf_obj_info = {} def create_volume(self, name, aggr, size): v = FlexVol(self, name) v.create(aggr, size) return v def flexshare_disable(self): """Equivalent to 'priority off' on the CLI.""" self.invoke('priority-disable') def flexshare_enable(self): """Equivalent to 'priority on' on the CLI.""" self.invoke('priority-enable') def flexshare_is_enabled(self): """Return boolean representing whether FlexShare is enabled.""" out = self.invoke('priority-list-info') if out.child_get_string('status') == 'on': return True else: return False def get_cifs_homedirs(self): """ Equivalent to 'cifs homedir' on the CLI. Return an array of cifs home directory paths. """ out = self.invoke('cifs-homedir-paths-get') if out.has_children(): homedirs = [] for d in out.child_get('homedir-paths').children_get(): homedirs.append(d.element['content']) return homedirs else: return [] def get_export(self, export_path): """ Return an Export object representing NFS share at export_path. If export does not exist, return False. """ if self.has_export(export_path): return (Export(self, export_path)) else: return False def get_exports(self): """Return a list of Export objects of filer's configured NFS shares.""" out = self.invoke('nfs-exportfs-list-rules') exports = [] for export in out.child_get('rules').children_get(): path = export.child_get_string('pathname') exports.append(Export(self, path)) return exports def get_fs_status_msg(self): """Return a string containing the file system status message.""" return self.get_oid('.1.3.6.1.4.1.789.1.5.7.2.0') def get_oid(self, oid): """Return a generic OID from the NetApp SNMP MIB.""" out = self.invoke('snmp-get', 'object-id', oid) return out.child_get_string('value') def get_perf_object(self, objectname, read=[], instances=[]): """ Return objectname's performance data in a dict tree. read - optional array of counters whose values are to be read. If not set, all counters are reported. instances - optional array of instances whose values are to be read. If not set, all instances are reported. """ info = self.get_perf_object_info(objectname) get_perf_obj = NaElement('perf-object-get-instances-iter-start') get_perf_obj.child_add(NaElement('objectname', objectname)) if read: read_counters = NaElement('counters') for c in read: read_counters.child_add(NaElement('counter', c)) get_perf_obj.child_add(read_counters) if instances: insts = NaElement('instances') for inst in instances: insts.child_add(NaElement('instance', inst)) get_perf_obj.child_add(insts) out = self.invoke_elem(get_perf_obj) iter_tag = out.child_get_int('tag') iter_recs = out.child_get_int('records') perf_insts = {} i = 0 while i < iter_recs: out = self.invoke('perf-object-get-instances-iter-next', 'maximum', 1, 'tag', iter_tag) inst = out.child_get('instances').child_get('instance-data') inst_name = inst.child_get_string('name') perf_insts[inst_name] = {} counters = inst.child_get('counters').children_get() for c in counters: name = c.child_get_string('name') if info[name]['type'] == 'array': vals = c.child_get_string('value').split(',') data = {} j = 0 while j < len(vals): data[info[name]['labels'][j]] = int(vals[j]) j = j + 1 perf_insts[inst_name][name] = data else: try: perf_insts[inst_name][name] = c.child_get_int('value') except ValueError: # Must be a string... perf_insts[inst_name][name] = c.child_get_string( 'value') i = i + 1 self.invoke('perf-object-get-instances-iter-end', 'tag', iter_tag) return perf_insts def get_perf_object_info(self, objectname): """ Return dict of static information about perf counter 'objectname'. Information is returned as a dict of dicts. """ # Check cache: if self.perf_obj_info.has_key(objectname): return self.perf_obj_info[objectname] out = self.invoke('perf-object-counter-list-info', 'objectname', objectname) counters = {} for counter in out.child_get('counters').children_get(): name = counter.child_get_string('name') counters[name] = {} counters[name]['desc'] = counter.child_get_string('desc') counters[name]['privilege-level'] = counter.child_get_string( 'privilege-level') ## Handle optional keys: for opt_key in ('base-counter', 'properties', 'type', 'unit'): opt_val = counter.child_get_string(opt_key) if opt_val: counters[name][opt_key] = opt_val elif opt_key == 'type': counters[name]['type'] = 'scalar' labels = counter.child_get('labels') if labels: counters[name]['labels'] = labels.child_get_string( 'label-info').split(',') # Store info in cache and return self.perf_obj_info[objectname] = counters return counters def get_perf_object_list(self): """Return dict of filer performance object names and privileges.""" out = self.invoke('perf-object-list-info') objs = {} for obj in out.child_get('objects').children_get(): objs[obj.child_get_string('name')] = obj.child_get_string( 'privilege-level') return objs def get_root_name(self): """Return a string containing the Filer's root volume's name.""" out = self.invoke('aggr-get-root-name') return out.child_get_string('root-volume') def get_share(self, name): """ Return a Share object representing the existing CIFS share of name. If share does not exist, return False. """ if self.has_share(name): return (Share(self, name)) else: return False def get_shares(self): """Return a list of Share objects containing filer's CIFS exports.""" out = self.invoke_cli('cifs', 'shares') # Pattern of output is two header lines, followed by each share name # starting at the left-hand side of the output. Regexp accounts # For share name being able to include whitespace and other # characters - match is anchored on first "/" following whitespace, # which is presumed to be the start of the mount point. output = out.child_get('cli-output').element['content'].splitlines() share_pattern = re.compile(r'^([a-zA-Z].*\S)\s+\/') shares = [] for line in output[2:]: m = share_pattern.match(line) if m: shares.append(Share(self, m.groups()[0])) return shares def get_option(self, name): """Equivalent to 'options <name>' on the CLI.""" out = self.invoke('options-get', 'name', name) return out.child_get_string('value') def get_volume(self, name): """Return FlexVol object of existing vol 'name'; else return False.""" if self.has_volume(name): return (FlexVol(self, name)) else: return False def get_volumes(self): """Retun a list of FlexVol objects that exist on filer.""" volumes = [] out = self.invoke('volume-list-info') for volume in out.child_get('volumes').children_get(): name = volume.child_get_string('name') volumes.append(FlexVol(self, name)) return volumes def has_export(self, path): """Check if filer has NFS export name; return boolean.""" export = Export(self, path) return export.configured() def has_share(self, name): """Check if filer has CIFS share name; return boolean.""" share = Share(self, name) return share.configured() def has_volume(self, name): """Check if filer has FlexVol name; return boolean.""" try: self.invoke('volume-list-info', 'volume', name) except OntapApiException as e: if e.errno == '13040': return False else: raise return True def invoke(self, *args): out = self.api.invoke(*args) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def invoke_cli(self, *cli_args): """ Call the unsupported/undocumented system-cli API. args is a tuple of arguments that, joined with spaces, would represent the command line if executing in the CLI. """ args = NaElement('args') for arg in cli_args: args.child_add(NaElement('arg', arg)) cli = NaElement('system-cli') cli.child_add(args) out = self.api.invoke_elem(cli) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def invoke_elem(self, naelement): """Call the NetApp API using an NaElement.""" out = self.api.invoke_elem(naelement) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def set_cifs_homedirs(self, homedirs): """Set the list of CIFS home directory paths for the filer.""" homedir_paths = NaElement('homedir-paths') for d in homedirs: homedir_paths.child_add(NaElement('homedir-path-info', d)) chps = NaElement('cifs-homedir-paths-set') chps.child_add(homedir_paths) self.invoke_elem(chps) def set_option(self, option, value): """Equivalent to 'options <option> <value>' on the CLI.""" self.invoke('options-set', 'name', option, 'value', value) def _xmltree_to_dict(self, out, int_values=(), key='name', value='value'): """Convert thinly-veiled XML from ONTAP API to a dict.""" options = {} for option in out.child_get('options').children_get(): name = option.child_get_string(key) if name in int_values: options[name] = option.child_get_int(value) else: options[name] = option.child_get_string(value) return options def _xmltree_to_list(self, nae, outer_name, inner_name): """ Return list converted from ONTAP API NaElement 'nae'. nae - NaElement from ONTAP API outer_name - outer 'child' of NaElement inner_name - inner 'child' of NaElement """ out_list = [] if nae.child_get(outer_name): for item in nae.child_get(outer_name).children_get(): inner_val = item.child_get_string(inner_name) if inner_val is not None: out_list.append(inner_val) return out_list
def cluster_setup(cluster): print("> " + cluster["cluster-name"] + ": Creating Cluster ") for node in cluster["cluster-nodes"]: print("---> " + node["node-name"] + ": Working on node ") # Building Session - REST s = requests.Session() s.url = "https://{}".format(node["ip"]) s.verify = False s.auth = (node["user"], node["password"]) s.headers = { "Content-Type": "application/hal+json", "Accept": "application/hal+json" } # Building Session - ZAPI session = NaServer(node["ip"], 1, 140) session.set_server_type("Filer") session.set_admin_user(node["user"], node["password"]) session.set_transport_type("HTTPS") # STEP: Create cluster if ("-01" in node["node-name"]): print("---> " + node["node-name"] + ": Creating cluster...") # create cluster API api = "/api/cluster" # creating body body = { "contact": cluster["contact"], "location": cluster["location"], "name": cluster["cluster-name"], "password": cluster["password"], "management_interface": {} } # add cluster mgmt for lif in cluster["net-interfaces"]: if (lif["role"] == "cluster-mgmt"): body["management_interface"]["ip"] = { "address": lif["address"], "netmask": lif["netmask"] } for route in cluster["net-routes"]: if (route["destination"] == "0.0.0.0/0"): body["management_interface"]["ip"][ "gateway"] = route["gateway"] break else: continue break else: continue # add ntp server #if (cluster.get("ntp-servers")): # for ntp_server in cluster["ntp-servers"]: # body["ntp_servers"].append(ntp_server["server-name"]) # add licenses #for ontap_license in cluster["licenses"]: # body["licenses"].append(cluster["licenses"][ontap_license]) response = s.post(url=s.url + api, data=json.dumps(body)) print("URL==" + s.url + api) #debug print("BODY==" + json.dumps(body)) #debug if response: print(response.json()) else: print("WE DIDNT GET RESPONSE") #sys.exit() status = handle_job(s, response) if (status == "success"): print("---> " + node["node-name"] + ": SUCCESS") else: print("---> " + node["node-name"] + ": " + status) sys.exit() # STEP: Reading cluster LIF IP for joining additional nodes later if ("-01" in node["node-name"]): print( "--- " + node["node-name"] + ": Reading cluster LIF IP for joining further nodes later...") api = NaElement("net-interface-modify") for lif in cluster["net-interfaces"]: if (lif["role"] == "cluster-mgmt"): api.child_add_string("home-port", lif["home-port"]) api.child_add_string("interface-name", "cluster_mgmt") api.child_add_string("vserver", cluster["cluster-name"]) else: continue xo = session.invoke_elem(api) if (xo.results_status() == "failed"): print("Error:\n") print(xo.sprintf()) sys.exit(1) print("Received:\n") print(xo.sprintf()) api1 = NaElement("net-interface-revert") api1.child_add_string("interface-name", "cluster_mgmt") api1.child_add_string("vserver", cluster["cluster-name"]) xo1 = session.invoke_elem(api1) if (xo1.results_status() == "failed"): print("Error:\n") print(xo1.sprintf()) sys.exit(1) print("Received:\n") print(xo1.sprintf()) api = "/api/network/ip/interfaces" url_params = "?fields=services,ip.address&services=cluster_core&max_records=1" response = s.get(url=s.url + api + url_params) status = handle_job(s, response) if (status == "success"): clus_lif_ip = response.json()["records"][0]["ip"]["address"] print("---> " + node["node-name"] + ": SUCCESS") else: print("---> " + node["node-name"] + ": " + status) sys.exit(1) # STEP: Join nodes to cluster if (not "-01" in node["node-name"]): print("--- " + node["node-name"] + ": Joining node to cluster...") zapi_post = NaElement("cluster-join") zapi_post.child_add_string("cluster-ip-address", clus_lif_ip) zapi_post_return = session.invoke_elem(zapi_post) if (zapi_post_return.results_status() == "failed"): print("---> " + node["node-name"] + ": " + zapi_post_return.sprintf().strip()) sys.exit(1) else: zapi_get = NaElement("cluster-create-join-progress-get") is_complete = "" join_iterator = 1 while is_complete != "true" and \ join_iterator < 13: time.sleep(10) zapi_get_return = session.invoke_elem(zapi_get) is_complete = zapi_get_return.child_get( "attributes").child_get( "cluster-create-join-progress-info" ).child_get_string("is-complete") action_status = zapi_get_return.child_get( "attributes").child_get( "cluster-create-join-progress-info" ).child_get_string("status") join_iterator = join_iterator + 1 if (is_complete == "true") and (action_status == "success"): print("---> " + node["node-name"] + ": SUCCESS") else: print("---> " + node["node-name"] + ": " + zapi_get.sprintf().strip()) print("---> " + node["node-name"] + ": " + zapi_get_return.sprintf().strip()) sys.exit(1)
class Filer: """A NetApp filer.""" def __init__(self, hostname, user, passwd): self.api = NaServer(hostname, 1, 3) self.api.set_style('LOGIN') self.api.set_admin_user(user, passwd) self.api.set_transport_type('HTTPS') self.name = hostname out = self.invoke('system-get-version') self.version = out.child_get_string('version') # Used for caching performance object descriptions: self.perf_obj_info = {} def create_volume(self, name, aggr, size): v = FlexVol(self, name) v.create(aggr, size) return v def flexshare_disable(self): """Equivalent to 'priority off' on the CLI.""" self.invoke('priority-disable') def flexshare_enable(self): """Equivalent to 'priority on' on the CLI.""" self.invoke('priority-enable') def flexshare_is_enabled(self): """Return boolean representing whether FlexShare is enabled.""" out = self.invoke('priority-list-info') if out.child_get_string('status') == 'on': return True else: return False def get_cifs_homedirs(self): """ Equivalent to 'cifs homedir' on the CLI. Return an array of cifs home directory paths. """ out = self.invoke('cifs-homedir-paths-get') if out.has_children(): homedirs = [] for d in out.child_get('homedir-paths').children_get(): homedirs.append(d.element['content']) return homedirs else: return [] def get_export(self, export_path): """ Return an Export object representing NFS share at export_path. If export does not exist, return False. """ if self.has_export(export_path): return(Export(self, export_path)) else: return False def get_exports(self): """Return a list of Export objects of filer's configured NFS shares.""" out = self.invoke('nfs-exportfs-list-rules') exports = [] for export in out.child_get('rules').children_get(): path = export.child_get_string('pathname') exports.append(Export(self, path)) return exports def get_fs_status_msg(self): """Return a string containing the file system status message.""" return self.get_oid('.1.3.6.1.4.1.789.1.5.7.2.0') def get_oid(self, oid): """Return a generic OID from the NetApp SNMP MIB.""" out = self.invoke('snmp-get', 'object-id', oid) return out.child_get_string('value') def get_perf_object(self, objectname, read=[], instances=[]): """ Return objectname's performance data in a dict tree. read - optional array of counters whose values are to be read. If not set, all counters are reported. instances - optional array of instances whose values are to be read. If not set, all instances are reported. """ info = self.get_perf_object_info(objectname) get_perf_obj = NaElement('perf-object-get-instances-iter-start') get_perf_obj.child_add(NaElement('objectname', objectname)) if read: read_counters = NaElement('counters') for c in read: read_counters.child_add(NaElement('counter', c)) get_perf_obj.child_add(read_counters) if instances: insts = NaElement('instances') for inst in instances: insts.child_add(NaElement('instance', inst)) get_perf_obj.child_add(insts) out = self.invoke_elem(get_perf_obj) iter_tag = out.child_get_int('tag') iter_recs = out.child_get_int('records') perf_insts = {} i = 0 while i < iter_recs: out = self.invoke('perf-object-get-instances-iter-next', 'maximum', 1, 'tag', iter_tag) inst = out.child_get('instances').child_get('instance-data') inst_name = inst.child_get_string('name') perf_insts[inst_name] = {} counters = inst.child_get('counters').children_get() for c in counters: name = c.child_get_string('name') if info[name]['type'] == 'array': vals = c.child_get_string('value').split(',') data = {} j = 0 while j < len(vals): data[info[name]['labels'][j]] = int(vals[j]) j = j + 1 perf_insts[inst_name][name] = data else: try: perf_insts[inst_name][name] = c.child_get_int('value') except ValueError: # Must be a string... perf_insts[inst_name][name] = c.child_get_string( 'value') i = i + 1 self.invoke('perf-object-get-instances-iter-end', 'tag', iter_tag) return perf_insts def get_perf_object_info(self, objectname): """ Return dict of static information about perf counter 'objectname'. Information is returned as a dict of dicts. """ # Check cache: if self.perf_obj_info.has_key(objectname): return self.perf_obj_info[objectname] out = self.invoke('perf-object-counter-list-info', 'objectname', objectname) counters = {} for counter in out.child_get('counters').children_get(): name = counter.child_get_string('name') counters[name] = {} counters[name]['desc'] = counter.child_get_string('desc') counters[name]['privilege-level'] = counter.child_get_string( 'privilege-level') ## Handle optional keys: for opt_key in ('base-counter', 'properties', 'type', 'unit'): opt_val = counter.child_get_string(opt_key) if opt_val: counters[name][opt_key] = opt_val elif opt_key == 'type': counters[name]['type'] = 'scalar' labels = counter.child_get('labels') if labels: counters[name]['labels'] = labels.child_get_string( 'label-info').split(',') # Store info in cache and return self.perf_obj_info[objectname] = counters return counters def get_perf_object_list(self): """Return dict of filer performance object names and privileges.""" out = self.invoke('perf-object-list-info') objs = {} for obj in out.child_get('objects').children_get(): objs[obj.child_get_string('name')] = obj.child_get_string( 'privilege-level') return objs def get_root_name(self): """Return a string containing the Filer's root volume's name.""" out = self.invoke('aggr-get-root-name') return out.child_get_string('root-volume') def get_share(self, name): """ Return a Share object representing the existing CIFS share of name. If share does not exist, return False. """ if self.has_share(name): return(Share(self, name)) else: return False def get_shares(self): """Return a list of Share objects containing filer's CIFS exports.""" out = self.invoke_cli('cifs', 'shares') # Pattern of output is two header lines, followed by each share name # starting at the left-hand side of the output. Regexp accounts # For share name being able to include whitespace and other # characters - match is anchored on first "/" following whitespace, # which is presumed to be the start of the mount point. output = out.child_get('cli-output').element['content'].splitlines() share_pattern = re.compile(r'^([a-zA-Z].*\S)\s+\/') shares = [] for line in output[2:]: m = share_pattern.match(line) if m: shares.append(Share(self, m.groups()[0])) return shares def get_option(self, name): """Equivalent to 'options <name>' on the CLI.""" out = self.invoke('options-get', 'name', name) return out.child_get_string('value') def get_volume(self, name): """Return FlexVol object of existing vol 'name'; else return False.""" if self.has_volume(name): return(FlexVol(self, name)) else: return False def get_volumes(self): """Retun a list of FlexVol objects that exist on filer.""" volumes = [] out = self.invoke('volume-list-info') for volume in out.child_get('volumes').children_get(): name = volume.child_get_string('name') volumes.append(FlexVol(self, name)) return volumes def has_export(self, path): """Check if filer has NFS export name; return boolean.""" export = Export(self, path) return export.configured() def has_share(self, name): """Check if filer has CIFS share name; return boolean.""" share = Share(self, name) return share.configured() def has_volume(self, name): """Check if filer has FlexVol name; return boolean.""" try: self.invoke('volume-list-info', 'volume', name) except OntapApiException as e: if e.errno == '13040': return False else: raise return True def invoke(self, *args): out = self.api.invoke(*args) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def invoke_cli(self, *cli_args): """ Call the unsupported/undocumented system-cli API. args is a tuple of arguments that, joined with spaces, would represent the command line if executing in the CLI. """ args = NaElement('args') for arg in cli_args: args.child_add(NaElement('arg', arg)) cli = NaElement('system-cli') cli.child_add(args) out = self.api.invoke_elem(cli) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def invoke_elem(self, naelement): """Call the NetApp API using an NaElement.""" out = self.api.invoke_elem(naelement) if out.results_status() == 'failed': raise OntapApiException(out.results_errno(), out.results_reason()) return out def set_cifs_homedirs(self, homedirs): """Set the list of CIFS home directory paths for the filer.""" homedir_paths = NaElement('homedir-paths') for d in homedirs: homedir_paths.child_add(NaElement('homedir-path-info', d)) chps = NaElement('cifs-homedir-paths-set') chps.child_add(homedir_paths) self.invoke_elem(chps) def set_option(self, option, value): """Equivalent to 'options <option> <value>' on the CLI.""" self.invoke('options-set', 'name', option, 'value', value) def _xmltree_to_dict(self, out, int_values=(), key='name', value='value'): """Convert thinly-veiled XML from ONTAP API to a dict.""" options = {} for option in out.child_get('options').children_get(): name = option.child_get_string(key) if name in int_values: options[name] = option.child_get_int(value) else: options[name] = option.child_get_string(value) return options def _xmltree_to_list(self, nae, outer_name, inner_name): """ Return list converted from ONTAP API NaElement 'nae'. nae - NaElement from ONTAP API outer_name - outer 'child' of NaElement inner_name - inner 'child' of NaElement """ out_list = [] if nae.child_get(outer_name): for item in nae.child_get(outer_name).children_get(): inner_val = item.child_get_string(inner_name) if inner_val is not None: out_list.append(inner_val) return out_list