def __init__(self, api_key=None, url=maas_url): if api_key == None: self.api_key = self.get_api_key() else: self.api_key = api_key self.auth = MAASOAuth(*self.api_key.split(':')) self.url = url self.client = MAASClient(self.auth, MAASDispatcher(), self.url)
def create_node(mac, arch, power_type, power_parameters): api_credentials = get_recorded_api_credentials() if api_credentials is None: raise Exception('Not creating node: no API key yet.') client = MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(), get_maas_url()) data = { 'op': 'new', 'architecture': arch, 'power_type': power_type, 'power_parameters': power_parameters, 'mac_addresses': mac } return client.post('/api/1.0/nodes/', data)
def make_client(root=None, result=None): """Create a MAASClient.""" if root is None: root = make_url() auth = MAASOAuth(factory.getRandomString(), factory.getRandomString(), factory.getRandomString()) return MAASClient(auth, FakeDispatcher(result=result), root)
def evaluate_tag( system_id, nodes, tag_name, tag_definition, tag_nsmap, credentials, maas_url, ): """Evaluate `tag_definition` against this cluster's nodes' details. :param system_id: System ID for the rack controller. :param nodes: List of nodes to evaluate. :param tag_name: The name of the tag, used for logging. :param tag_definition: The XPath expression of the tag. :param tag_nsmap: The namespace map as used by LXML's ETree library. :param credentials: A 3-tuple of OAuth credentials. :param maas_url: URL of the MAAS API. """ # Turn off proxy detection, since the rack should talk directly to # the region, even if a system-wide proxy is configured. client = MAASClient( auth=MAASOAuth(*credentials), dispatcher=MAASDispatcher(autodetect_proxies=False), base_url=maas_url, ) process_node_tags( rack_id=system_id, nodes=nodes, tag_name=tag_name, tag_definition=tag_definition, tag_nsmap=tag_nsmap, client=client, )
def create_node(mac, arch, power_type, power_parameters): api_credentials = get_recorded_api_credentials() if api_credentials is None: raise Exception('Not creating node: no API key yet.') client = MAASClient( MAASOAuth(*api_credentials), MAASDispatcher(), get_maas_url()) data = { 'op': 'new', 'architecture': arch, 'power_type': power_type, 'power_parameters': power_parameters, 'mac_addresses': mac } return client.post('/api/1.0/nodes/', data)
def evaluate_tag( system_id, nodes, tag_name, tag_definition, tag_nsmap, credentials, maas_url, ): """Evaluate `tag_definition` against this cluster's nodes' details. :param system_id: System ID for the rack controller. :param nodes: List of nodes to evaluate. :param tag_name: The name of the tag, used for logging. :param tag_definition: The XPath expression of the tag. :param tag_nsmap: The namespace map as used by LXML's ETree library. :param credentials: A 3-tuple of OAuth credentials. :param maas_url: URL of the MAAS API. """ client = MAASClient( auth=MAASOAuth(*credentials), dispatcher=MAASDispatcher(), base_url=maas_url, ) process_node_tags( rack_id=system_id, nodes=nodes, tag_name=tag_name, tag_definition=tag_definition, tag_nsmap=tag_nsmap, client=client, )
def submit(maas_url, api_credentials, images): """Submit images to server.""" MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(), maas_url).post('api/1.0/boot-images/', 'report_boot_images', nodegroup=get_cluster_uuid(), images=json.dumps(images))
def make_client(root=None, result=None): """Create a MAASClient.""" if root is None: root = factory.make_simple_http_url(path=factory.make_name("path") + "/") auth = MAASOAuth(factory.make_string(), factory.make_string(), factory.make_string()) return MAASClient(auth, FakeDispatcher(result=result), root)
def update_region_controller(knowledge, interface, server): """Update the region controller with the status of the probe. :param knowledge: dictionary of server info :param interface: name of interface, e.g. eth0 :param server: IP address of detected DHCP server, or None """ api_path = 'api/1.0/nodegroups/%s/interfaces/%s/' % ( knowledge['nodegroup_uuid'], interface) oauth = MAASOAuth(*knowledge['api_credentials']) client = MAASClient(oauth, MAASDispatcher(), knowledge['maas_url']) if server is None: server = '' process_request(client.put, api_path, foreign_dhcp_ip=server)
def get_cached_knowledge(): """Get all the information that we need to know, or raise an error. :return: (client, nodegroup_uuid) """ api_credentials = get_recorded_api_credentials() if api_credentials is None: logger.error("Not updating tags: don't have API key yet.") return None, None nodegroup_uuid = get_recorded_nodegroup_uuid() if nodegroup_uuid is None: logger.error("Not updating tags: don't have UUID yet.") return None, None client = MAASClient(MAASOAuth(*api_credentials), MAASDispatcher(), get_maas_url()) return client, nodegroup_uuid
def _getclient(url=u'http://localhost/MAAS/api/1.0/'): ''' Use the MAAS apiclient to aquire a session with the Maas API :param url: How to connect to the Maas server. As we require the Maas tools installed on the executing machine, this can be the localhost by default. :return: A MAASClient object ''' global _mclient if _mclient is None: consumer_key, token, secret = key('root').split(':', 3) auth = MAASOAuth(consumer_key, token, secret) dispatch = MAASDispatcher() _mclient = MAASClient(auth, dispatch, url) return _mclient
class MaasTagging(object): def __init__(self): """ Main execution path """ self.conn = None self.read_settings() self.run() def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read( os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) def run(self): """ Make calls to Maas to create tags and assign nodes to them """ self._connect() # Get system ids of nodes nodes = self.maas_client.get('/nodes/', 'list').read() system_ids = {} for node in json.loads(nodes): system_ids[node['hostname'].split('.')[0]] = node['system_id'] # Get existing tags tags = self.maas_client.get('/tags/', 'list').read() tags = [tag['name'] for tag in json.loads(tags)] # Create tags if not existing and apply them for tag in self.tags: if not tag in tags: params = {'name': tag} self.maas_client.post('/tags/', 'new', **params) nodes = self.tags[tag].split(',') for node in nodes: params = {'add': system_ids[node]} self.maas_client.post('/tags/%s/' % tag, 'update_nodes', **params)
class MaasTagging(object): def __init__(self): """ Main execution path """ self.conn = None self.read_settings() self.run() def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) def run(self): """ Make calls to Maas to create tags and assign nodes to them """ self._connect() # Get system ids of nodes nodes = self.maas_client.get('/nodes/', 'list').read() system_ids = {} for node in json.loads(nodes): system_ids[node['hostname'].split('.')[0]] = node['system_id'] # Get existing tags tags = self.maas_client.get('/tags/', 'list').read() tags = [tag['name'] for tag in json.loads(tags)] # Create tags if not existing and apply them for tag in self.tags: if not tag in tags: params = {'name': tag} self.maas_client.post('/tags/', 'new', **params) nodes = self.tags[tag].split(',') for node in nodes: params = {'add': system_ids[node]} self.maas_client.post('/tags/%s/' % tag, 'update_nodes', **params)
def determine_cluster_interfaces(knowledge): """Given server knowledge, determine network interfaces on this cluster. :return: a list of tuples of (interface name, ip) for all interfaces. :note: this uses an API call and not local probing because the region controller has the definitive and final say in what does and doesn't exist. """ api_path = 'api/1.0/nodegroups/%s/interfaces' % knowledge['nodegroup_uuid'] oauth = MAASOAuth(*knowledge['api_credentials']) client = MAASClient(oauth, MAASDispatcher(), knowledge['maas_url']) interfaces = process_request(client.get, api_path, 'list') if interfaces is None: return None interface_names = sorted((interface['interface'], interface['ip']) for interface in interfaces if interface['interface'] != '') return interface_names
def send_leases(leases): """Send lease updates to the server API.""" # Items that the server must have sent us before we can do this. knowledge = { 'maas_url': get_maas_url(), 'api_credentials': get_recorded_api_credentials(), 'nodegroup_uuid': get_recorded_nodegroup_uuid(), } if None in knowledge.values(): # The MAAS server hasn't sent us enough information for us to do # this yet. Leave it for another time. logger.info( "Not sending DHCP leases to server: not all required knowledge " "received from server yet. " "Missing: %s" % ', '.join(list_missing_items(knowledge))) return api_path = 'api/1.0/nodegroups/%s/' % knowledge['nodegroup_uuid'] oauth = MAASOAuth(*knowledge['api_credentials']) MAASClient(oauth, MAASDispatcher(), knowledge['maas_url']).post(api_path, 'update_leases', leases=json.dumps(leases))
class FabricMAAS(object): CORD_TEST_HOST = '172.17.0.1' head_node = os.getenv('HEAD_NODE', CORD_TEST_HOST) maas_url = 'http://{}/MAAS/api/1.0/'.format(head_node) def __init__(self, api_key=None, url=maas_url): if api_key == None: self.api_key = self.get_api_key() else: self.api_key = api_key self.auth = MAASOAuth(*self.api_key.split(':')) self.url = url self.client = MAASClient(self.auth, MAASDispatcher(), self.url) @classmethod def get_api_key(cls): api_key = os.getenv('MAAS_API_KEY', None) if api_key: return api_key cmd = ['maas-region-admin', 'apikey', '--username=cord'] try: p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except: return 'UNKNOWN' out, err = p.communicate() if err: raise Exception('Cannot get api key for MAAS') return out.strip() def get_node_list(self): nodes = self.client.get(u'nodes/', 'list').read() node_list = json.loads(nodes) hosts = [self.head_node] + map(lambda n: n['hostname'], node_list) return hosts
def _auth(**connection_args): ''' Set up maas credentials Only intended to be used within maas-enabled modules ''' prefix = "maas." # look in connection_args first, then default to config file def get(key, default=None): return connection_args.get( 'connection_' + key, __salt__['config.get'](prefix + key, default)) api_token = get('token') api_url = get('url', 'https://localhost/') LOG.debug("MAAS url: " + api_url) LOG.debug("MAAS token: " + api_token) auth = MAASOAuth(*api_token.split(":")) dispatcher = MAASDispatcher() client = MAASClient(auth, dispatcher, api_url) return client
def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True
def fake_client(self): return MAASClient(None, None, self.make_maas_url())
def fake_client(self): return MAASClient(None, None, factory.make_simple_http_url())
class MaasInventory(object): def __init__(self): """ Main execution path """ self.conn = None self.inventory = dict() # A list of groups and the hosts in that group self.cache = dict() # Details about hosts in the inventory # Read settings and parse CLI arguments self.read_settings() self.parse_cli_args() # Cache if self.args.refresh_cache: self.update_cache() elif not self.is_cache_valid(): self.update_cache() else: self.load_inventory_from_cache() self.load_cache_from_cache() data_to_print = "" # Data to print if self.args.host: data_to_print = self.get_host_info() elif self.args.list: # Display list of instances for inventory data_to_print = self.json_format_dict(self.inventory, True) else: # default action with no options data_to_print = self.json_format_dict(self.inventory, True) print data_to_print def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def is_cache_valid(self): """ Determines if the cache files have expired, or if it is still valid """ if os.path.isfile(self.cache_path_cache): mod_time = os.path.getmtime(self.cache_path_cache) current_time = time() if (mod_time + self.cache_max_age) > current_time: if os.path.isfile(self.cache_path_inventory): return True return False def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) family = dict(config.items('family')) groups = [] for parent in family: group = { 'name': parent, 'children': family[parent].split(','), 'vars': dict(config.items(parent+':vars')) } groups.append(group) self.ansible_groups = groups host_vars = dict(config.items('host:vars')) host_vars['devices'] = host_vars['devices'].split(',') host_vars['raw_journal_devices'] = host_vars['raw_journal_devices'].split(',') self.ansible_host_vars = host_vars # Cache related cache_path = config.get('maas', 'cache_path') self.cache_path_cache = cache_path + "/ansible-maas.cache" self.cache_path_inventory = cache_path + "/ansible-maas.index" self.cache_max_age = config.getint('maas', 'cache_max_age') def parse_cli_args(self): """ Command line argument processing """ parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Maas') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to Maas (default: False - use cache files)') self.args = parser.parse_args() def update_cache(self): """ Make calls to Maas and save the output in a cache """ self._connect() self.groups = dict() self.hosts = dict() hostvars = dict() for tag in self.tags: nodes = self.maas_client.get('/tags/' + tag, 'nodes').read() nodes = json.loads(nodes) hosts = [] for node in nodes: hostname = node['hostname'] ip_address = node['ip_addresses'][0] if not hostname in hostvars: hostvars[hostname] = dict() hostvars[hostname].update(self.ansible_host_vars) hostvars[hostname]['ansible_ssh_host'] = ip_address hosts.append(hostname) self.inventory[tag] = {'hosts': hosts} self.inventory['_meta'] = {'hostvars': hostvars} for group in self.ansible_groups: self.inventory[group['name']] = { 'children': group['children'], 'vars': group['vars'] } self.write_to_cache(self.cache, self.cache_path_cache) self.write_to_cache(self.inventory, self.cache_path_inventory) def get_host_info(self): """ Get variables about a specific host """ if not self.cache or len(self.cache) == 0: # Need to load index from cache self.load_cache_from_cache() if not self.args.host in self.cache: # try updating the cache self.update_cache() if not self.args.host in self.cache: # host might not exist anymore return self.json_format_dict({}, True) return self.json_format_dict(self.cache[self.args.host], True) def push(self, my_dict, key, element): """ Pushed an element onto an array that may not have been defined in the dict """ if key in my_dict: my_dict[key].append(element) else: my_dict[key] = [element] def load_inventory_from_cache(self): """ Reads the index from the cache file sets self.index """ cache = open(self.cache_path_inventory, 'r') json_inventory = cache.read() self.inventory = json.loads(json_inventory) def load_cache_from_cache(self): """ Reads the cache from the cache file sets self.cache """ cache = open(self.cache_path_cache, 'r') json_cache = cache.read() self.cache = json.loads(json_cache) def write_to_cache(self, data, filename): """ Writes data in JSON format to a file """ json_data = self.json_format_dict(data, True) cache = open(filename, 'w') cache.write(json_data) cache.close() def to_safe(self, word): """ Converts 'bad' characters in a string to underscores so they can be used as Ansible groups """ return re.sub("[^A-Za-z0-9\-]", "_", word) def json_format_dict(self, data, pretty=False): """ Converts a dict to a JSON object and dumps it as a formatted string """ if pretty: return json.dumps(data, sort_keys=True, indent=2) else: return json.dumps(data)
def get_client(url, creds): [consumer_key, token, secret] = creds.split(':') auth = MAASOAuth(consumer_key=consumer_key, resource_token=token, resource_secret=secret) return MAASClient(auth, MAASDispatcher(), url)
def make_anonymous_api_client(server_url): """Create an unauthenticated API client.""" return MAASClient(NoAuth(), MAASDispatcher(), server_url)
def _getclient(url=u'http://localhost/MAAS/api/1.0/'): consumer_key, token, secret = key('root').split(':', 3) auth = MAASOAuth(consumer_key, token, secret) dispatch = MAASDispatcher() client = MAASClient(auth, dispatch, url) return client