def _login(): """ :returns: process status :rtype: int """ # every call to login will generate a new token if applicable _logout() dcos_url = config.get_config_val("core.dcos_url") if dcos_url is None: msg = ("Please provide the url to your DC/OS cluster: " "`dcos config set core.dcos_url`") raise DCOSException(msg) # hit protected endpoint which will prompt for auth if cluster has auth try: url = urllib.parse.urljoin(dcos_url, 'exhibitor') http.get(url) # if the user is authenticated, they have effectively "logged in" even if # they are not authorized for this endpoint except DCOSAuthorizationException: pass emitter.publish("Login successful!") return 0
def setup_cluster_config(dcos_url, temp_path, stored_cert): """ Create a cluster directory for cluster specified in "temp_path" directory. :param dcos_url: url to DC/OS cluster :type dcos_url: str :param temp_path: path to temporary config dir :type temp_path: str :param stored_cert: whether we stored cert bundle in 'setup' dir :type stored_cert: bool :returns: path to cluster specific directory :rtype: str """ try: # find cluster id cluster_url = dcos_url.rstrip('/') + '/metadata' res = http.get(cluster_url, timeout=1) cluster_id = res.json().get("CLUSTER_ID") except DCOSException as e: msg = ("Error trying to find cluster id: {}\n " "Please make sure the provided DC/OS URL is valid: {}".format( e, dcos_url)) raise DCOSException(msg) # create cluster id dir cluster_path = os.path.join(config.get_config_dir_path(), constants.DCOS_CLUSTERS_SUBDIR, cluster_id) if os.path.exists(cluster_path): raise DCOSException("Cluster [{}] is already setup".format(dcos_url)) util.ensure_dir_exists(cluster_path) # move contents of setup dir to new location for (path, dirnames, filenames) in os.walk(temp_path): for f in filenames: util.sh_copy(os.path.join(path, f), cluster_path) cluster = Cluster(cluster_id) config_path = cluster.get_config_path() if stored_cert: cert_path = os.path.join(cluster_path, "dcos_ca.crt") config.set_val("core.ssl_verify", cert_path, config_path=config_path) cluster_name = cluster_id try: url = dcos_url.rstrip('/') + '/mesos/state-summary' name_query = http.get(url, toml_config=cluster.get_config()) cluster_name = name_query.json().get("cluster") except DCOSException: pass config.set_val("cluster.name", cluster_name, config_path=config_path) return cluster_path
def has_capability(self, capability): """Check if cluster has a capability. :param capability: capability name :type capability: string :return: does the cluster has capability :rtype: bool """ if not self.enabled(): return False try: url = urllib.parse.urljoin(self.cosmos_url, 'capabilities') response = http.get(url, headers=_get_capabilities_header()).json() except Exception as e: logger.exception(e) return False if 'capabilities' not in response: logger.error( 'Request to get cluster capabilities: {} ' 'returned unexpected response: {}. ' 'Missing "capabilities" field'.format(url, response)) return False return {'name': capability} in response['capabilities']
def print_components(ip, use_json): """ Print components for a given node ip. The data is taked from 3dt endpoint: /system/health/v1/nodes/<ip>/units :param ip: DC/OS node ip address :type ip: str :param use_json: print components in json format :type use_json: bool """ dcos_url = config.get_config_val('core.dcos_url').rstrip("/") if not dcos_url: raise config.missing_config_exception(['core.dcos_url']) url = dcos_url + '/system/health/v1/nodes/{}/units'.format(ip) response = http.get(url).json() if 'units' not in response: raise DCOSException( 'Invalid response. Missing field `units`. {}'.format(response)) if use_json: emitter.publish(response['units']) else: for component in response['units']: emitter.publish(component['id'])
def get_maintenance_status(dcos_client, agents=[]): """ :param dcos_client: DCOSClient :type dcos_client: DCOSClient :params hostnames: list of hostnames :type: list :returns: list of dict of maintenance status, list found in maintenance status :rtype: list,list """ maintenance_status = [] try: url = dcos_client.master_url('maintenance/status') req = http.get(url).json() # XXX: to refactor if 'draining_machines' in req: maintenance_status.extend( lookup_and_tag(req['draining_machines'], "DRAINING", agents, "id")) if 'down_machines' in req: maintenance_status.extend( lookup_and_tag(req['down_machines'], "DOWN", agents, None)) return maintenance_status except DCOSException as e: logger.exception(e) except Exception as (e): raise DCOSException( "Unable to fetch maintenance status from mesos master: " + str(e))
def service_available_predicate(service_name): url = dcos_service_url(service_name) try: response = http.get(url) return response.status_code == 200 except Exception as e: return False
def get_app(self, app_id, version=None): """Returns a representation of the requested application version. If version is None the return the latest version. :param app_id: the ID of the application :type app_id: str :param version: application version as a ISO8601 datetime :type version: str :returns: the requested Marathon application :rtype: dict """ app_id = self.normalize_app_id(app_id) if version is None: url = self._create_url('v2/apps{}'.format(app_id)) else: url = self._create_url( 'v2/apps{}/versions/{}'.format(app_id, version)) response = http.get(url, to_exception=_to_exception) # Looks like Marathon return different JSON for versions if version is None: return response.json()['app'] else: return response.json()
def get_resource(resource): """ :param resource: optional filename or http(s) url for the application or group resource :type resource: str :returns: resource :rtype: dict """ if resource is not None: if os.path.isfile(resource): with util.open_file(resource) as resource_file: return util.load_json(resource_file) else: try: http.silence_requests_warnings() req = http.get(resource) if req.status_code == 200: data = b'' for chunk in req.iter_content(1024): data += chunk return util.load_jsons(data.decode('utf-8')) else: raise Exception except Exception: raise DCOSException( "Can't read from resource: {0}.\n" "Please check that it exists.".format(resource))
def _get_versions(dcos_url): """Print DC/OS and DC/OS-CLI versions :param dcos_url: url to DC/OS cluster :type dcos_url: str :returns: Process status :rtype: int """ dcos_info = {} try: url = urllib.parse.urljoin( dcos_url, 'dcos-metadata/dcos-version.json') res = http.get(url, timeout=1) if res.status_code == 200: dcos_info = res.json() except Exception as e: logger.exception(e) pass emitter.publish( "dcoscli.version={}\n".format(dcoscli.version) + "dcos.version={}\n".format(dcos_info.get("version", "N/A")) + "dcos.commit={}\n".format(dcos_info.get( "dcos-image-commit", "N/A")) + "dcos.bootstrap-id={}".format(dcos_info.get("bootstrap-id", "N/A")) ) return 0
def enabled(self): """Returns whether or not cosmos is enabled on specified dcos cluter :rtype: bool """ try: url = urllib.parse.urljoin(self.cosmos_url, 'capabilities') response = http.get(url, headers=_get_capabilities_header()) # return `Authentication failed` error messages except DCOSAuthenticationException: raise # Authorization errors mean endpoint exists, and user could be # authorized for the command specified, not this endpoint except DCOSAuthorizationException: return True # allow exception through so we can show user actual http exception # except 404, because then the url is fine, just not cosmos enabled except DCOSHTTPException as e: logger.exception(e) return e.status() != 404 except Exception as e: logger.exception(e) return True return response.status_code == 200
def http_get_marathon_path(name, marathon_name='marathon'): """Invokes HTTP GET for marathon url with name. For example, name='ping': http GET {dcos_url}/service/marathon/ping """ url = get_marathon_endpoint(name, marathon_name) headers = {'Accept': '*/*'} return http.get(url, headers=headers)
def test_metric_endpoint(marathon_service_name): response = http.get("{}metrics".format( shakedown.dcos_service_url(marathon_service_name))) assert response.status_code == 200 print(response.json()['gauges']) assert response.json( )['gauges']['service.mesosphere.marathon.app.count'] is not None
def ensure_permissions(): common.set_service_account_permissions(MOM_EE_SERVICE_ACCOUNT) url = urljoin(shakedown.dcos_url(), 'acs/api/v1/acls/dcos:superuser/users/{}'.format(MOM_EE_SERVICE_ACCOUNT)) req = http.get(url) expected = '/acs/api/v1/acls/dcos:superuser/users/{}/full'.format(MOM_EE_SERVICE_ACCOUNT) assert req.json()['array'][0]['url'] == expected, "Service account permissions couldn't be set"
def get_arangodb_framework(name): url = config.get_config_val('core.dcos_url') + "/mesos/state.json" try: response = http.get(url, timeout=15) except DCOSException as e: print("cannot connect to '" + url + "'", e) sys.exit(1) if response.status_code >= 200 and response.status_code < 300: json = response.json() if 'frameworks' not in json: print(json) sys.exit(1) frameworks = json['frameworks'] for framework in frameworks: if name == framework['name']: return framework print("ArangoDB framework '" + name + "' is not running yet.") sys.exit(1) else: print("Bad response getting master state. Status code: " + str(response.status_code)) sys.exit(1)
def has_capability(self, capability): """Check if cluster has a capability. :param capability: capability name :type capability: string :return: does the cluster has capability :rtype: bool """ if not self.enabled(): return False try: url = urllib.parse.urljoin(self.cosmos_url, 'capabilities') response = http.get(url, headers=_get_capabilities_header()).json() except Exception as e: logger.exception(e) return False if 'capabilities' not in response: logger.error('Request to get cluster capabilities: {} ' 'returned unexpected response: {}. ' 'Missing "capabilities" field'.format(url, response)) return False return {'name': capability} in response['capabilities']
def get_resource(name): """ :param name: optional filename or http(s) url for the application or group resource :type name: str | None :returns: resource :rtype: dict """ if name is not None: if os.path.isfile(name): with util.open_file(name) as resource_file: return util.load_json(resource_file) else: try: http.silence_requests_warnings() req = http.get(name) if req.status_code == 200: data = b'' for chunk in req.iter_content(1024): data += chunk return util.load_jsons(data.decode('utf-8')) else: raise Exception except Exception: logger.exception('Cannot read from resource %s', name) raise DCOSException( "Can't read from resource: {0}.\n" "Please check that it exists.".format(name)) example = "E.g.: dcos marathon app add < app_resource.json" ResourceReader._assert_no_tty(example) return util.load_json(sys.stdin)
def copy_to_cache(self, target_dir): """Copies the source content to the supplied local directory. :param target_dir: Path to the destination directory. :type target_dir: str :returns: The error, if one occurred :rtype: None """ try: with util.tempdir() as tmp_dir: tmp_file = os.path.join(tmp_dir, 'packages.zip') # Download the zip file. req = http.get(self.url) if req.status_code == 200: with open(tmp_file, 'wb') as f: for chunk in req.iter_content(1024): f.write(chunk) else: raise Exception( 'HTTP GET for {} did not return 200: {}'.format( self.url, req.status_code)) # Unzip the downloaded file. packages_zip = zipfile.ZipFile(tmp_file, 'r') packages_zip.extractall(tmp_dir) # Move the enclosing directory to the target directory enclosing_dirs = [ item for item in os.listdir(tmp_dir) if os.path.isdir(os.path.join(tmp_dir, item)) ] # There should only be one directory present after extracting. assert (len(enclosing_dirs) is 1) enclosing_dir = os.path.join(tmp_dir, enclosing_dirs[0]) shutil.copytree(enclosing_dir, target_dir) # Set appropriate file permissions on the scripts. x_mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP) scripts_dir = os.path.join(target_dir, 'scripts') scripts = os.listdir(scripts_dir) for script in scripts: script_path = os.path.join(scripts_dir, script) if os.path.isfile(script_path): os.chmod(script_path, x_mode) return None except Exception: logger.exception('Unable to fetch packages from URL: %s', self.url) raise DCOSException('Unable to fetch packages from [{}]'.format( self.url))
def browse(self, slave, path): """ GET /files/browse.json Request path:... # path to run ls on Response [ { path: # full path to file nlink: size: mtime: mode: uid: gid: } ] :param slave: slave to issue the request on :type slave: Slave :returns: /files/browse.json response :rtype: dict """ url = self.slave_url(slave['id'], slave.http_url(), 'files/browse.json') return http.get(url, params={'path': path}).json()
def logging_strategy(): """ function returns logging strategy :return: logging strategy. :rtype: str """ # default strategy is sandbox logging. strategy = 'logrotate' if not has_journald_capability(): return strategy base_url = config.get_config_val("core.dcos_url") url = urllib.parse.urljoin(base_url, '/dcos-metadata/ui-config.json') if not base_url: raise config.missing_config_exception(['core.dcos_url']) try: response = http.get(url).json() except (DCOSAuthenticationException, DCOSAuthorizationException): raise except DCOSException: emitter.publish('Unable to determine logging mechanism for ' 'your cluster. Defaulting to files API.') return strategy try: strategy = response['uiConfiguration']['plugins']['mesos'][ 'logging-strategy'] # noqa: ignore=F403,E501 except Exception: pass return strategy
def slave_file_read(self, slave_id, private_url, path, offset, length): """See the master_file_read() docs :param slave_id: slave ID :type slave_id: str :param path: absolute path to read :type path: str :param private_url: The slave's private URL derived from its pid. Used when we're accessing mesos directly, rather than through DCOS. :type private_url: str :param offset: start byte location, or -1. -1 means read no data, and is used to fetch the size of the file in the response's 'offset' parameter. :type offset: int :param length: number of bytes to read, or -1. -1 means read the whole file :type length: int :returns: files/read.json response :rtype: dict """ url = self.slave_url(slave_id, private_url, 'files/read.json') params = {'path': path, 'length': length, 'offset': offset} return http.get(url, params=params, timeout=self._timeout).json()
def get_app_versions(self, app_id, max_count=None): """Asks Marathon for all the versions of the Application up to a maximum count. :param app_id: the ID of the application or group :type app_id: str :param id_type: type of the id ("apps" or "groups") :type app_id: str :param max_count: the maximum number of version to fetch :type max_count: int :returns: a list of all the version of the application :rtype: [str] """ if max_count is not None and max_count <= 0: raise DCOSException( 'Maximum count must be a positive number: {}'.format(max_count) ) app_id = self.normalize_app_id(app_id) url = self._create_url('v2/apps{}/versions'.format(app_id)) response = http.get(url, to_exception=_to_exception) if max_count is None: return response.json()['versions'] else: return response.json()['versions'][:max_count]
def print_logs_range(url): """ Make a get request to `dcos-log` range endpoint. the function will print out logs to stdout and exit. :param url: `dcos-log` endpoint :type url: str """ with contextlib.closing( http.get(url, is_success=is_success, headers={'Accept': 'text/plain'})) as r: if r.status_code == 500: raise DCOSException('{} Error message: {}'.format( DCOSHTTPException(r), r.text)) if r.status_code == 404: raise DCOSException('No files exist. Exiting.') if r.status_code == 204: raise DCOSException('No logs found') if r.status_code != 200: raise DCOSException('Error getting logs. Url: {};' 'response code: {}'.format(url, r.status_code)) for line in r.iter_lines(): emitter.publish(line.decode('utf-8', 'ignore'))
def test_ui_available(): """ This simply confirms that a URL call the service endpoint is successful if MoM is launched. """ response = http.get("{}/ui/".format( shakedown.dcos_service_url('marathon-user'))) assert response.status_code == 200
def test_ui_available(marathon_service_name): """Simply verifies that a request to the UI endpoint is successful if Marathon is launched.""" response = http.get("{}/ui/".format( shakedown.dcos_service_url(marathon_service_name))) assert response.status_code == 200, "HTTP status code is {}, but 200 was expected".format( response.status_code)
def slave_file_read(self, slave_id, private_url, path, offset, length): """See the master_file_read() docs :param slave_id: slave ID :type slave_id: str :param path: absolute path to read :type path: str :param private_url: The slave's private URL derived from its pid. Used when we're accessing mesos directly, rather than through DC/OS. :type private_url: str :param offset: start byte location, or -1. -1 means read no data, and is used to fetch the size of the file in the response's 'offset' parameter. :type offset: int :param length: number of bytes to read, or -1. -1 means read the whole file :type length: int :returns: files/read.json response :rtype: dict """ url = self.slave_url(slave_id, private_url, 'files/read.json') params = {'path': path, 'length': length, 'offset': offset} return http.get(url, params=params, timeout=self._timeout).json()
def overlay_info(self): # XXX This methodology is copied from the dcos.mesos package, this can # be removed once the upstream package has support for this field. url = self.dcos_client.master_url('overlay-agent/overlay') timeout = dcosconfig.get_config_val('core.timeout', dcosconfig.get_config()) return dcoshttp.get(url, timeout=timeout).json()
def _get_versions(dcos_url): """Print DCOS and DCOS-CLI versions :param dcos_url: url to DCOS cluster :type dcos_url: str :returns: Process status :rtype: int """ dcos_info = {} try: url = urllib.parse.urljoin( dcos_url, 'dcos-metadata/dcos-version.json') res = http.get(url, timeout=1) if res.status_code == 200: dcos_info = res.json() except Exception as e: logger.exception(e) pass emitter.publish( "dcoscli.version={}\n".format(dcoscli.version) + "dcos.version={}\n".format(dcos_info.get("version", "N/A")) + "dcos.commit={}\n".format(dcos_info.get( "dcos-image-commit", "N/A")) + "dcos.bootstrap-id={}".format(dcos_info.get("bootstrap-id", "N/A")) ) return 0
def wait_for_metronome(): """ Waits for the Metronome API url to be available for a given timeout. """ url = metronome_api_url() try: response = http.get(url) assert response.status_code == 200, f"Expecting Metronome service to be up but it did not get healthy after 5 minutes. Last response: {response.content}" # noqa except Exception as e: assert False, f"Expecting Metronome service to be up but it did not get healthy after 5 minutes. Last exception: {e}" # noqa
def metadata(self): """ Get /metadata :returns: /metadata content :rtype: dict """ url = self.get_dcos_url('metadata') return http.get(url, timeout=self._timeout).json()
def test_ui_available(marathon_service_name): """ This simply confirms that a URL call the service endpoint is successful if marathon is launched. """ response = http.get("{}/ui/".format( shakedown.dcos_service_url(marathon_service_name))) assert response.status_code == 200, "HTTP code: {} is NOT 200".format(response.status_code)
def test_ui_available(marathon_service_name): """ This simply confirms that a URL call the service endpoint is successful if marathon is launched. """ response = http.get("{}/ui/".format( shakedown.dcos_service_url(marathon_service_name))) assert response.status_code == 200
def metadata(self): """ GET /metadata :returns: /metadata content :rtype: dict """ url = self.get_dcos_url('metadata') return http.get(url, timeout=self._timeout).json()
def get_state(self): """Get the Mesos master state json object :returns: Mesos' master state json object :rtype: dict """ return http.get(self._create_url('master/state.json')).json()
def wait_for_metronome(): """ Waits for the Metronome API url to be available for a given timeout. """ url = shakedown.dcos_url_path("/service/metronome/v1/jobs") try: response = http.get(url) assert response.status_code == 200, f"Expecting Metronome service to be up but it did not get healthy after 5 minutes. Last response: {response.content}" # noqa except Exception as e: assert False, f"Expecting Metronome service to be up but it did not get healthy after 5 minutes. Last exception: {e}" # noqa
def get_state_summary(self): """Get the Mesos master state summary json object :returns: Mesos' master state summary json object :rtype: dict """ url = self.master_url('master/state-summary') return http.get(url, timeout=self._timeout).json()
def service_unavailable_predicate(service_name): url = dcos_service_url(service_name) try: response = http.get(url) except DCOSHTTPException as e: if e.response.status_code == 500: return True else: return False
def test_metric_endpoint(marathon_service_name): service_url = shakedown.dcos_service_url(marathon_service_name) response = http.get("{}metrics".format(service_url)) assert response.status_code == 200, "HTTP status code {} is NOT 200".format(response.status_code) response_json = response.json() print(response_json['gauges']) assert response_json['gauges']['service.mesosphere.marathon.app.count'] is not None, \ "service.mesosphere.marathon.app.count is absent"
def _cosmos_request(self, url, http_request_type, headers_preference, data=None, json=None, **kwargs): """ Gets a Response object obtained by calling cosmos at the url 'url'. Will attempt each of the headers in headers_preference in order until success. :param url: the url of a cosmos endpoint :type url: str :param headers_preference: a list of request headers in order of preference. Each header will be attempted until they all fail or the request succeeds. :type headers_preference: list[dict[str, str]] :param data: the request's body :type data: dict | bytes | file-like object :param json: JSON request body :type json: dict :param kwargs: Additional arguments to requests.request (see py:func:`request`) :type kwargs: dict :return: response returned by calling cosmos at url :rtype: requests.Response """ try: headers = headers_preference[0] if http_request_type is 'post': response = http.post(url, data=data, json=json, headers=headers, **kwargs) else: response = http.get(url, data=data, json=json, headers=headers, **kwargs) if not _matches_expected_response_header(headers, response.headers): raise DCOSException('Server returned incorrect response type, ' 'expected {} but got {}'.format( headers.get('Accept'), response.headers.get('Content-Type'))) return response except DCOSBadRequest as e: if len(headers_preference) > 1: # reattempt with one less item in headers_preference return self._cosmos_request(url, http_request_type, headers_preference[1:], data, json, **kwargs) else: raise e
def get_master_state(self): """Get the Mesos master state json object :returns: Mesos' master state json object :rtype: dict """ url = self.master_url('master/state.json') return http.get(url, timeout=self._timeout).json()
def hosts(self, host): """ GET v1/hosts/<host> :param host: host :type host: str :returns: {'ip', 'host'} dictionary :rtype: dict(str, str) """ url = self._path('v1/hosts/{}'.format(host)) return http.get(url, headers={}).json()
def _cosmos_request(self, url, http_request_type, headers_preference, data=None, json=None, **kwargs): """ Gets a Response object obtained by calling cosmos at the url 'url'. Will attempt each of the headers in headers_preference in order until success. :param url: the url of a cosmos endpoint :type url: str :param headers_preference: a list of request headers in order of preference. Each header will be attempted until they all fail or the request succeeds. :type headers_preference: list[dict[str, str]] :param data: the request's body :type data: dict | bytes | file-like object :param json: JSON request body :type json: dict :param kwargs: Additional arguments to requests.request (see py:func:`request`) :type kwargs: dict :return: response returned by calling cosmos at url :rtype: requests.Response """ try: headers = headers_preference[0] if http_request_type is 'post': response = http.post( url, data=data, json=json, headers=headers, **kwargs) else: response = http.get( url, data=data, json=json, headers=headers, **kwargs) if not _matches_expected_response_header(headers, response.headers): raise DCOSException( 'Server returned incorrect response type, ' 'expected {} but got {}'.format( headers.get('Accept'), response.headers.get('Content-Type'))) return response except DCOSBadRequest as e: if len(headers_preference) > 1: # reattempt with one less item in headers_preference return self._cosmos_request(url, http_request_type, headers_preference[1:], data, json, **kwargs) else: raise e
def get_leader(self): """ Get the leading marathon instance. :returns: string of the form <ip>:<port> :rtype: str """ url = self._create_url("v2/leader") response = http.get(url, timeout=self._timeout) return response.json()["leader"]
def get_slave_state(self, slave_id): """Get the Mesos slave state json object :param slave_id: slave ID :type slave_id: str :returns: Mesos' master state json object :rtype: dict """ url = self.slave_url(slave_id, 'state.json') return http.get(url, timeout=self._timeout).json()
def get_apps(self): """Get a list of known applications. :returns: list of known applications :rtype: [dict] """ url = self._create_url('v2/apps') response = http.get(url, to_exception=_to_exception) return response.json()['apps']
def get_apps(self): """Get a list of known applications. :returns: list of known applications :rtype: [dict] """ url = self._create_url("v2/apps") response = http.get(url, to_exception=_to_exception, timeout=self._timeout) return response.json()["apps"]
def get_about(self): """Returns info about Marathon instance :returns Marathon information :rtype: dict """ url = self._create_url('v2/info') response = http.get(url, to_exception=_to_exception) return response.json()
def get_groups(self): """Get a list of known groups. :returns: list of known groups :rtype: list of dict """ url = self._create_url('v2/groups') response = http.get(url, to_exception=_to_exception) return response.json()['groups']