def __init__(self, mode, response_file): if mode != Prompts.PROMPT_MODE and mode != Prompts.RECORD_MODE and mode != Prompts.HEADLESS_MODE: raise InstallException('Invalid prompt mode %s' % str(mode)) self.response_file = None self.mode = mode self.recorded_options = dict() self.headless_parser = None if mode == Prompts.HEADLESS_MODE: if response_file is None: raise InstallException( 'In order to run in headless mode, you must supply a response file.' ) if not os.path.exists(response_file): raise InstallException( 'In order to run in headless mode, you must supply an existing response file. %s does not exist.' % response_file) self.headless_parser = Parser.get_properties_parser(response_file) Log.info( 'Installer prompts have been initialized in headless mode.') elif mode == Prompts.RECORD_MODE: if response_file is None: raise InstallException( 'In order to run in record mode, you must supply a response file destination.' ) dirname = os.path.dirname(response_file) if not os.path.exists(dirname): raise InstallException( 'In order to run in record mode, the directory for the response file must exist.' ) self.response_file = open(response_file, 'w') Log.info( 'Installer prompts have been initialized in record mode. response file: {0}' .format(response_file))
def invoke_gcloud(cmd): response, status = OSCommand.run2("gcloud {0}".format(cmd)) if status != 0: Log.error("Could not create GKE Cluster: {0}: {1}".format( status, response)) BootstrapBase.exit_application(101) return response, status
def prompt_file(self, prompt_text, default=None, newline=False, key_name=None): while True: result = self.prompt(prompt_text, default, newline=newline, key_name=key_name) if result is None: if self.mode == Prompts.HEADLESS_MODE: raise InstallException( 'File path cannot be blank for key %s.' % key_name) Log.warning('Path cannot be blank.') continue if not os.path.exists(result): if self.mode == Prompts.HEADLESS_MODE: raise InstallException( 'File path %s does not exist for key %s.' % (result, key_name)) Log.warning('Path to %s does not exist' % result) continue break return result
def _create_update_resource_group(self): Log.info( "Creating or updating resource group: {0}...".format( self.context.resource_group), True) resource = ResourceGroup(location=self.context.location) self.resource_client.resource_groups.create_or_update( self.context.resource_group, resource)
def build_cloud(self): self.cluster_name = self.prompts.prompt("Enter cluster name", self.cluster_name, key_name="GKE_CLUSTER_NAME") self.nodes = self.prompts.prompt_integer("Enter number of nodes", self.nodes, 1, key_name="GKE_NODE_COUNT") self.disks = self.prompts.prompt_integer( "Enter number of local SSD disks for MapR FS. Each disk will be a fixed 375GB", self.disks, 1, key_name="GKE_NUM_DISKS") self.instance_type = self.prompts.prompt("GKE compute instance type?", self.instance_type, key_name="GKE_INSTANCE_TYPE") if self.alpha: self.k8s_version = self.prompts.prompt("Kubernetes version?", self.k8s_alpha_version, key_name="GKE_K8S_VERSION") else: self.k8s_version = self.prompts.prompt("Kubernetes version?", self.k8s_version, key_name="GKE_K8S_VERSION") self.zone = self.prompts.prompt("GCE Zone to deploy into?", self.zone, key_name="GKE_ZONE") self.project = self.prompts.prompt("GCE project id?", self.project, key_name="GKE_PROJECT_ID") self.user = self.prompts.prompt("GCE user id?", self.user, key_name="GKE_USER") # self.image_type = self.prompts.prompt("GKE image type?", self.image_type) Log.info("Using GKE compute image type: {0}".format(self.image_type), True) if self.alpha: Log.info("Using alpha Kubernetes version", True) else: Log.info("Using non-alpha Kubernetes version", True) if not self.prompts.prompt_boolean( "Ready to create Google GKS cluster. Do you want to continue?", True, key_name="GKE_CREATE"): Log.error("Exiiting since user is not ready to continue") BootstrapBase.exit_application(100) before = time.time() self.create_k8s_cluster() after = time.time() diff = int(after - before) Log.info( "Cluster creation took {0}m {1}s".format(diff / 60, diff % 60), True)
def confirm_delete_installation(self): print(os.linesep) Log.info( "This will uninstall ALL MapR operators from your Kubernetes environment. This will cause all Compute Spaces to be destroyed. They cannot be recovered!", True) agree = self._prompts.prompt_boolean("Do you agree?", False, key_name="AGREEMENT") if not agree: Log.info("Very wise decision. Exiting uninstall...", True) BootstrapBase.exit_application(2)
def check_if_storage(self): print(os.linesep) agree = self._prompts.prompt_boolean("Install MapR Data Platform?", True, key_name="CREATE_STORAGE") Log.info( "Attention: MapR Data Platform on Kubernetes is PRE-ALPHA Software. Please DO NOT store critical data in clusters you create with this " "operator. You WILL lose data and these clusters WILL NOT be upgradable.", stdout=True) print("") return agree
def check_available(): available_instances = dict() for cloud_name, cloud_instance in Cloud._cloud_instances.items(): if cloud_instance.is_available(): Log.debug("{0} cloud is available".format(cloud_name)) available_instances[cloud_name] = cloud_instance else: Log.warning( "{0} cloud was enabled but did not pass availability tests" .format(cloud_name)) Cloud._cloud_instances = available_instances
def prompt_not_none(self, prompt_text, default=None, password=False, newline=False, key_name=None): while True: result = self.prompt(prompt_text, default, password, newline, key_name) if result is not None: result = result.strip() if len(result) > 0: return result Log.warning('Invalid: %s. Please re enter a non empty value.' % prompt_text)
def process_labels(self): self._get_json() nodes_not_set = self.get_mapr_use_node_labels(NodeLabels.MAPR_LABEL) if nodes_not_set is not None and len(nodes_not_set) > 0: Log.info("Setting MapR usage tag {0} for {1} nodes...".format(NodeLabels.MAPR_LABEL, len(nodes_not_set)), stdout=True) for node_not_set in nodes_not_set: self.k8s.run_label_mapr_node(node_not_set, NodeLabels.MAPR_LABEL, True) nodes_not_set = self.get_mapr_use_node_labels(NodeLabels.EXCLUSIVE_LABEL) if nodes_not_set is not None and len(nodes_not_set) > 0: Log.info("Setting MapR usage tag {0} for {1} nodes...".format(NodeLabels.EXCLUSIVE_LABEL, len(nodes_not_set)), stdout=True) for node_not_set in nodes_not_set: self.k8s.run_label_mapr_node(node_not_set, NodeLabels.EXCLUSIVE_LABEL, "None")
def validate_nodes(self): print(os.linesep) Log.info( "We must validate and annotate your Kubernetes nodes. " "MapR node validation pods will be installed.", stdout=True) agree = self._prompts.prompt_boolean("Do you agree?", True, key_name="AGREEMENT_VALIDATE") if not agree: Log.error("Exiting due to non-agreement...") BootstrapBase.exit_application(2) # TODO: Add node exclusion code here # exclude = self._prompts.prompt_boolean("Do you want to exclude any nodes?", False, key_name="EXCLUDE_NODES") # if exclude: # Log.error("Operation not currently supported...") # BootstrapBase.exit_application(6) print("")
def invoke_stable_cluster(self, args): cmd = "container --project {0} clusters create {1} {2}".format( self.project, self.cluster_name, args) Log.info("kubernetes version = {0}".format(self.k8s_version), True) Log.info( "Creating GKE environment via the following command: gcloud {0}". format(cmd), True) Log.info("Create log follows...") result, status = self.invoke_gcloud(cmd) Log.info(result, True)
def configure_cloud(self): cmd = GoogleCloud.CMD_ROLE_BINDING.format(self.user) Log.info("Now we will configure RBAC for your kubernetes env...", True) Log.info( "Binding cluster-admin role to GCE user: {0}...".format(self.user), True) response, status = OSCommand.run2(cmd) if status != 0: Log.error("Could not bind cluster-admin role: {0}:{1}", status, response) Log.info("Configured GKE permissions", True)
def run(self): logdir = os.path.join(self.script_dir, "logs") if os.path.exists(logdir): if not os.path.isdir(logdir): print( "ERROR: {0} is not a directory and cannot be used as alog directory" .format(logdir)) BootstrapBase.exit_application(1) else: os.mkdir(logdir) logname = os.path.join( logdir, BootstrapBase.NOW.strftime("bootstrap-%m-%d_%H:%M:%S.log")) Log.initialize(self.log_config_file, logname) BootstrapBase._prompts = Prompts.initialize(self.prompt_mode, self.prompt_response_file) Log.info("Prompt mode: {0}, response file: {1}".format( self.prompt_mode, self.prompt_response_file))
def collect(self): Log.debug('Checking kubectl is installed correctly...') response, status = OSCommand.run2("command -v kubectl") if status == 0: Log.info("Looking good... Found kubectl") self.operation = Validator.OPERATION_OK else: self.operation = Validator.OPERATION_INSTALL Log.error( "You will need to have kubectl installed on this machine.") Log.error( "To install kubectl please see: https://kubernetes.io/docs/tasks/tools/install-kubectl/" )
def collect(self): Log.debug('Checking for oc (OpenShift CLI) installation...') response, status = OSCommand.run2("command -v oc") if status == 0: Log.info("Looking good... Found oc (OpenShift CLI) installed", True) self.operation = Validator.OPERATION_OK else: self.operation = Validator.OPERATION_NONE Log.error( "You will need to have oc (OpenShift CLI) installed on this machine." ) Log.error( "To install oc please see: https://docs.openshift.com/container-platform/3.11/cli_reference/get_started_cli.html" )
def configure_kubernetes(self): print(os.linesep) Log.info("Ensuring proper kubernetes configuration...", True) Log.info("Checking kubectl can connect to your kubernetes cluster...", True) response, status = OSCommand.run2("kubectl get nodes") if status != 0: Log.error( "Cannot connect to Kubernetes. Make sure kubectl is pre-configured to communicate with a Kubernetes cluster." ) BootstrapBase.exit_application(4) Log.info("Looking good... Connected to Kubernetes", True) if self.cloud_instance is not None: self.cloud_instance.configure_cloud()
def prologue(self): title = os.linesep + "MapR for Kubernetes Bootstrap " title += "Installer" if self.is_install is True else "Uninstaller" title += " (Version {0})".format(BOOTSTRAP_BUILD_VERSION_NO) Log.info(title, True) Log.info("Copyright 2019 MapR Technologies, Inc., All Rights Reserved", True) Log.info("https://mapr.com/legal/eula/", True)
def run2(statements, username=None, use_nohup=False, out_file=None, in_background=False, users_env=False): if isinstance(statements, str): statements = [statements] responses = '' status = 0 for statement in statements: new_statement = '' if use_nohup: new_statement += 'nohup ' if username is not None: new_statement += 'su ' if users_env: new_statement += '- ' new_statement += username + ' -c \'' + statement + '\'' else: new_statement += statement if in_background: if use_nohup and out_file is not None: new_statement += ' > ' + out_file + ' 2>&1' else: new_statement += ' &>/dev/null' new_statement += ' &' Log.debug('RUN: %s' % new_statement) process = subprocess.Popen('%s 2>&1' % new_statement, shell=True, stdout=subprocess.PIPE) response = process.stdout.read() # process.wait will only return None if the process hasn't terminated. We don't # need to check for None here status = process.wait() if len(response) == 0: response = '<no response>' else: # Python 3 returns byes or bytearray from the read() above if not isinstance(response, str) and isinstance( response, (bytes, bytearray)): response = response.decode("UTF-8") Log.debug('STATUS: %s' % str(status)) Log.debug('RESPONSE: %s' % response) responses += response if status != 0: break return responses, status
def _create_aks(self): with open(os.path.join(self.base_path, CreateAKS.PARAMETERS_JSON)) as in_file: parameters = json.load(in_file) parameters[u"resourceName"][u"value"] = self.context.aks_name parameters[u"agentCount"][u"value"] = self.context.node_count parameters[u"agentVMSize"][u'Value'] = self.context.vm_size parameters[u"osDiskSizeGB"][u'Value'] = int(self.context.os_disk_size) parameters[u"sshRSAPublicKey"][u'Value'] = self.context.public_key parameters[u"servicePrincipalClientId"][ u'Value'] = self.context.client_id parameters[u"servicePrincipalClientSecret"][ u'Value'] = self.context.secret parameters[u"kubernetesVersion"][u'Value'] = self.context.k8s_version parameters[u"dnsPrefix"][u'Value'] = "{0}-maprtech".format( self.context.aks_name) with open(os.path.join(self.base_path, CreateAKS.ARM_TEMPLATE_JSON)) as in_file: template = json.load(in_file) deployment_properties = DeploymentProperties() deployment_properties.template = template deployment_properties.parameters = parameters deployment_properties.mode = DeploymentMode.incremental Log.info( "{0}: Run K8S deployment test with {1} Nodes OS Disk Size {2}...". format(self.context.resource_group, self.context.node_count, self.context.os_disk_size), True) deployment_async_operation = self.resource_client.deployments.create_or_update( self.context.resource_group, "maprk8s.deployment", deployment_properties) deployment_async_operation.wait() Log.info("K8S cluster deployment complete", True)
def exit_application(signum, _=None): if signum == 0: Log.info("Bootstrap terminated {0}".format(signum)) else: print(os.linesep) Log.warning("Bootstrap terminated {0}".format(signum)) if BootstrapBase._prompts is not None: BootstrapBase._prompts.write_response_file() BootstrapBase._prompts = None Log.close() exit(signum)
def is_available(self): if not self.enabled: return False if self.available is None: Log.info("Checking Google cloud availability. One moment...", True) results, status = OSCommand.run2( ["command -v gcloud", "gcloud compute instances list"]) self.available = True if status == 0 else False if not self.available: Log.warning( "Google Cloud SDK not found or not configured correctly. Quit bootstrapper, install and " "confgure Google Cloud SDK and restart bootstrapper. See: https://cloud.google.com/sdk/. " "More information on the error in the bootstrapper log here: " + Log.get_log_filename()) Log.warning(results) return self.available
def prompt_choices(self, prompt_text, choices, default=None, newline=False, key_name=None): if type(choices) is not list: raise InstallException('prompt_choices choice list is not a list.') choices_str = str(choices).replace('[', '(').replace(']', ')') prompt_text = '%s %s' % (prompt_text, choices_str) last_find = None if default is not None and default not in choices: raise InstallException( 'The default value %s was not found in the list' % default) while True: response = self.prompt(prompt_text, default, newline=newline, key_name=key_name) find_count = 0 if response is None: if self.mode == Prompts.HEADLESS_MODE: raise InstallException( '%s is not a choice in list %s for key %s.' % (response, choices, key_name)) Log.warning('Enter one of the choices in the list') continue for item in choices: if item.lower().find(response.lower()) >= 0: find_count += 1 last_find = item if find_count == 0: Log.warning('You must enter one of the choices in the list') elif find_count >= 2: Log.warning('Your entry matched more than one choice') else: break return last_find
def _get_json(self): Log.info("Retrieving node information...", stdout=True) result, status = self.k8s.run_get("nodes -o=json") if status != 0: return None self._json = json.loads(result) if self._json is None: Log.error("No JSON was returned from get nodes command") return self._items = self._json.get("items") if self._items is None: Log.error("No items dictonary in get nodes JSON") return self._node_count = len(self._items)
def get_mapr_use_node_labels(self, label): nodes_set = 0 nodes_not_set = set() for node in self._items: node_name = node["metadata"]["name"] mapr_usenode = node["metadata"]["labels"].get(label) if mapr_usenode is not None: nodes_set += 1 Log.info("Node: {0} has {1} label set to: {2}".format(node_name, label, mapr_usenode)) else: nodes_not_set.add(node_name) Log.info("Node: {0} does not have {1} label set".format(node_name, label)) Log.info("{0} node(s) found, {1} node(s) tagged with the MapR usage tag {2} while {3} node(s) not" .format(self._node_count, nodes_set, label, len(nodes_not_set)), stdout=True) return nodes_not_set
def run(self): # vms = self.compute_client.virtual_machines.list(self.resource_group) # for vm in vms: # nic = vm.network_profile.network_interfaces # pass nics = self.network_client.network_interfaces.list(self.resource_group) for nic in nics: ipconfig = nic.ip_configurations[0] public_ip_name = "{0}-publicip".format(nic.name) public_ip = { 'location': self.location, 'public_ip_allocation_method': 'Dynamic' } if ipconfig.public_ip_address is not None: Log.info( "{0} NIC already has a public ip address".format(nic.name), True) continue Log.info( "Creating or updating public ip address {0}...".format( public_ip_name), True) ip_rslt = self.network_client.public_ip_addresses.create_or_update( self.resource_group, public_ip_name, public_ip) ip_rslt.wait() public_ip = self.network_client.public_ip_addresses.get( self.resource_group, public_ip_name) ipconfig.public_ip_address = public_ip params = { 'location': self.location, 'ip_configurations': [ipconfig] } Log.info( "Associate public ip address {0} with NIC {1}".format( public_ip_name, nic.name), True) ip_rslt = self.network_client.network_interfaces.create_or_update( self.resource_group, nic.name, params) ip_rslt.wait()
def write_response_file(self): if Prompts._instance is None: Log.debug( 'Cannot write a response file prompts are not initialized') return if self.mode != Prompts.RECORD_MODE: Log.debug('Cannot write a response file when not in record mode') return if self.response_file.closed: Log.warn( 'Cannot write a response file that is not opened. Response file can only be written once' ) return try: sorted_keys = sorted(self.recorded_options) for key in sorted_keys: self.response_file.write( '%s=%s%s' % (key, self.recorded_options[key], os.linesep)) finally: self.response_file.close()
def __init__(self, env, compute_client): self.env = env self.compute_client = compute_client self.aks_rg = self.env.get("RESOURCE_GROUP") self.aks_name = self.env.get("AKS_NAME") self.location = self.env.get("LOCATION") self.data_disk_count = self.env.get_int("DATA_DISK_COUNT") self.data_disk_size = self.env.get_int("DATA_DISK_SIZE") self.data_disk_type = self.env.get("DATA_DISK_TYPE") self.resource_group = "MC_{0}_{1}_{2}".format(self.aks_rg, self.aks_name, self.location) if self.data_disk_type is None: Log.info("DATA_DISK_TYPE not supplied, using Standard_LRS", True) self.data_disk_type = StorageAccountTypes.standard_lrs elif (self.data_disk_type != "Standard_LRS") and \ (self.data_disk_type != "StandardSSD_LRS") and \ (self.data_disk_type != "Premium_LRS"): Log.info("Invalid disk type {0}, using Standard_LRS".format(self.data_disk_type), True) self.data_disk_type = StorageAccountTypes.standard_lrs else: Log.info("Creating data disks of type; {0}".format(self.data_disk_type), True)
def get_clients(self): Log.info("Getting Azure clients...", True) self.resource_client = ResourceManagementClient(self.credentials, self.context.subscription_id) self.compute_client = ComputeManagementClient(self.credentials, self.context.subscription_id) self.network_client = NetworkManagementClient(self.credentials, self.context.subscription_id) self.monitor_client = MonitorClient(self.credentials, self.context.subscription_id)
def pas_complete_installation(): Log.info("PAS Installation complete")