def test_set_maas_url_accepts_very_short_hostnames(self): config = RegionConfiguration({}) example_url = factory.make_simple_http_url(netloc=factory.make_string( size=1)) config.maas_url = example_url self.assertEqual(example_url, config.maas_url) self.assertEqual({"maas_url": example_url}, config.store)
def test__set_and_get(self): config = RegionConfiguration({}) workers = random.randint(8, 12) config.num_workers = workers self.assertEqual(workers, config.num_workers) # It's also stored in the configuration database. self.assertEqual({"num_workers": workers}, config.store)
def test_set_and_get_maas_url(self): config = RegionConfiguration({}) example_url = factory.make_simple_http_url() config.maas_url = example_url self.assertEqual(example_url, config.maas_url) # It's also stored in the configuration database. self.assertEqual({"maas_url": example_url}, config.store)
def test_set_maas_url_accepts_ipv6_addresses_with_brackets(self): config = RegionConfiguration({}) example_url = factory.make_simple_http_url(netloc="[%s]" % factory.make_ipv6_address()) config.maas_url = example_url self.assertEqual(example_url, config.maas_url) self.assertEqual({"maas_url": example_url}, config.store)
def global_options(request): version = get_maas_version_ui() uuid = RegionController.objects.get_or_create_uuid() with RegionConfiguration.open() as config: maas_url = config.maas_url user_completed_intro = False if hasattr(request.user, 'userprofile'): user_completed_intro = request.user.userprofile.completed_intro if request.user.is_authenticated: analytics_user_id = '%s-user%d' % (uuid, request.user.id) else: analytics_user_id = '%s-anon' % uuid return { 'global_options': { 'site_name': Config.objects.get_config('maas_name'), 'enable_analytics': Config.objects.get_config('enable_analytics'), }, 'debug': settings.DEBUG, 'version': version, 'files_version': version.replace(" ", ""), 'doc_version': get_maas_doc_version(), 'register_url': maas_url, 'register_secret': Config.objects.get_config('rpc_shared_secret'), 'completed_intro': Config.objects.get_config('completed_intro'), 'user_completed_intro': user_completed_intro, 'analytics_user_id': analytics_user_id, 'maas_uuid': uuid }
def absolute_url_reverse(view_name, query=None, *args, **kwargs): """Returns the absolute path (i.e. starting with '/') for the given view. This utility is meant to be used by methods that need to compute URLs but run outside of Django and thus don't have the 'script prefix' transparently added the the URL. :param view_name: Django's view function name/reference or URL pattern name for which to compute the absolute URL. :param query: Optional query argument which will be passed down to urllib.urlencode. The result of that call will be appended to the resulting url. :param args: Positional arguments for Django's 'reverse' method. :param kwargs: Named arguments for Django's 'reverse' method. """ with RegionConfiguration.open() as config: abs_path = urlparse(config.maas_url).path if not abs_path.endswith('/'): # Add trailing '/' to get urljoin to behave. abs_path = abs_path + '/' reverse_link = reverse(view_name, *args, **kwargs) if reverse_link.startswith('/'): # Drop the leading '/'. reverse_link = reverse_link[1:] url = urljoin(abs_path, reverse_link) if query is not None: url += '?%s' % urlencode(query, doseq=True) return url
def absolute_reverse(view_name, default_region_ip=None, query=None, base_url=None, *args, **kwargs): """Return the absolute URL (i.e. including the URL scheme specifier and the network location of the MAAS server). Internally this method simply calls Django's 'reverse' method and prefixes the result of that call with the configured MAAS URL. Consult the 'maas-region local_config_set --default-url' command for details on how to set the MAAS URL. :param view_name: Django's view function name/reference or URL pattern name for which to compute the absolute URL. :param default_region_ip: The default source IP address that should be used for the region controller. :param query: Optional query argument which will be passed down to urllib.urlencode. The result of that call will be appended to the resulting url. :param base_url: Optional url used as base. If None is provided, then configured MAAS URL will be used. :param args: Positional arguments for Django's 'reverse' method. :param kwargs: Named arguments for Django's 'reverse' method. """ if not base_url: with RegionConfiguration.open() as config: base_url = config.maas_url if default_region_ip is not None: base_url = compose_URL(base_url, default_region_ip) url = urljoin(base_url, reverse(view_name, *args, **kwargs)) if query is not None: url += '?%s' % urlencode(query, doseq=True) return url
def test__dumps_plain_string_to_stdout(self): stdio = call_get(**{self.option: True, "dump": config.dump_plain}) settings = stdio.getOutput() self.assertThat(settings, Not(Contains(self.option))) with RegionConfiguration.open() as configuration: self.assertThat(settings, Contains(str(getattr(configuration, self.option))))
def test__options_are_saved(self): self.useFixture(RegionConfigurationFixture()) # Set the option to a random value. if self.option == "database_port": value = factory.pick_port() elif self.option == "database_conn_max_age": value = random.randint(0, 60) elif self.option == "num_workers": value = random.randint(1, 16) elif self.option in ["debug", "debug_queries"]: value = random.choice(['true', 'false']) else: value = factory.make_name("foobar") # Values are coming from the command-line so stringify. stdio = call_set(**{self.option: str(value)}) # Nothing is echoed back to the user. self.assertThat(stdio.getOutput(), Equals("")) self.assertThat(stdio.getError(), Equals("")) # Some validators alter the given option, like adding an HTTP scheme # to a "bare" URL, so we merely check that the value contains the # given value, not that it exactly matches. Values are converted to a # str so Contains works with int values. with RegionConfiguration.open() as configuration: self.assertThat(str(getattr(configuration, self.option)), Contains(str(value)))
def inner_start_up(master=False): """Startup jobs that must run serialized w.r.t. other starting servers.""" # Register our MAC data type with psycopg. register_mac_type(connection.cursor()) # All commissioning and testing scripts are stored in the database. For # a commissioning ScriptSet to be created Scripts must exist first. Call # this early, only on the master process, to ensure they exist and are # only created once. If get_or_create_running_controller() is called before # this it will fail on first run. if master: load_builtin_scripts() # Ensure the this region is represented in the database. The first regiond # to pass through inner_start_up on this host can do this; it should NOT # be restricted to masters only. This also ensures that the MAAS ID is set # on the filesystem; it will be done in a post-commit hook and will thus # happen before `locks.startup` is released. node = RegionController.objects.get_or_create_running_controller() # Update region version ControllerInfo.objects.set_version(node, get_running_version()) # Ensure that uuid is created after creating RegionController.objects.get_or_create_uuid() # Only perform the following if the master process for the # region controller. if master: # Freshen the kms SRV records. dns_kms_setting_changed() # Make sure the commissioning distro series is still a supported LTS. commissioning_distro_series = Config.objects.get_config( name="commissioning_distro_series" ) ubuntu = UbuntuOS() if commissioning_distro_series not in ( ubuntu.get_supported_commissioning_releases() ): Config.objects.set_config( "commissioning_distro_series", ubuntu.get_default_commissioning_release(), ) Notification.objects.create_info_for_admins( "Ubuntu %s is no longer a supported commissioning " "series. Ubuntu %s has been automatically selected." % ( commissioning_distro_series, ubuntu.get_default_commissioning_release(), ), ident="commissioning_release_deprecated", ) with RegionConfiguration.open() as config: Config.objects.set_config("maas_url", config.maas_url) # Update deprecation notifications if needed sync_deprecation_notifications()
def handle(self, *args, **options): with RegionConfiguration.open() as config: output = { name: getattr(config, name) for name, option in gen_configuration_options() if options.get(name) } dump = options["dump"] dump(output)
def test__set_and_get(self): config = RegionConfiguration({}) example_value = random.choice(["true", "yes", "True"]) # Argument values will most often be passed in from the command-line, # so convert to a string before use to reflect that usage. setattr(config, self.option, example_value) self.assertTrue(getattr(config, self.option)) # It's also stored in the configuration database. self.assertEqual({self.option: True}, config.store)
def test_register_info(self): admin = factory.make_admin() handler = ControllerHandler(admin, {}, None) observed = handler.register_info({}) rpc_shared_secret = Config.objects.get_config("rpc_shared_secret") with RegionConfiguration.open() as config: maas_url = config.maas_url self.assertEqual( {"url": maas_url, "secret": rpc_shared_secret}, observed )
def handle(self, *args, **options): with RegionConfiguration.open_for_update() as config: for name, option in gen_configuration_options(): value = options.get(name) if value is not None: try: setattr(config, name, value) except formencode.Invalid as error: message = str(error).rstrip(".") raise CommandError("%s: %s." % (name.replace("_", "-"), message))
def test__set_and_get(self): config = RegionConfiguration({}) if isinstance(getattr(config, self.option), str): example_value = factory.make_name(self.option) else: example_value = factory.pick_port() # Argument values will most often be passed in from the command-line, # so convert to a string before use to reflect that usage. setattr(config, self.option, str(example_value)) self.assertEqual(example_value, getattr(config, self.option)) # It's also stored in the configuration database. self.assertEqual({self.option: example_value}, config.store)
def absolute_reverse( view_name, default_region_ip=None, query=None, base_url=None, *args, **kwargs, ): """Return the absolute URL (i.e. including the URL scheme specifier and the network location of the MAAS server). Internally this method simply calls Django's 'reverse' method and prefixes the result of that call with the configured MAAS URL. Consult the 'maas-region local_config_set --default-url' command for details on how to set the MAAS URL. :param view_name: Django's view function name/reference or URL pattern name for which to compute the absolute URL. :param default_region_ip: The default source IP address that should be used for the region controller. :param query: Optional query argument which will be passed down to urllib.urlencode. The result of that call will be appended to the resulting url. :param base_url: Optional url used as base. If None is provided, then configured MAAS URL will be used. :param args: Positional arguments for Django's 'reverse' method. :param kwargs: Named arguments for Django's 'reverse' method. """ if not base_url: with RegionConfiguration.open() as config: base_url = config.maas_url if default_region_ip is not None: base_url = compose_URL(base_url, default_region_ip) if not base_url.endswith("/"): # Add trailing '/' to get urljoin to behave. base_url = base_url + "/" if view_name.startswith("/"): reverse_link = view_name else: reverse_link = reverse(view_name, *args, **kwargs) if reverse_link.startswith("/"): # Drop the leading '/'. reverse_link = reverse_link[1:] script_name = settings.FORCE_SCRIPT_NAME.lstrip("/") if base_url.endswith(script_name) and reverse_link.startswith(script_name): # This would double up the SCRIPT_NAME we only need one so remove the # prefix from the reverse_link. reverse_link = reverse_link[len(script_name):] url = urljoin(base_url, reverse_link) if query is not None: url += "?%s" % urlencode(query, doseq=True) return url
def register_info(self, params): """Return the registration info for a new controller. User must be a superuser to perform this action. """ if not self.user.is_superuser: raise HandlerPermissionError() rpc_shared_secret = Config.objects.get_config("rpc_shared_secret") with RegionConfiguration.open() as config: maas_url = config.maas_url return {"url": maas_url, "secret": rpc_shared_secret}
def get_maas_facing_server_host(rack_controller=None): """Return configured MAAS server hostname, for use by nodes or workers. :param rack_controller: The `RackController` from the point of view of which the server host should be computed. :return: Hostname or IP address, as configured in the MAAS URL config setting or as configured on rack_controller.url. """ if rack_controller is None or not rack_controller.url: with RegionConfiguration.open() as config: maas_url = config.maas_url else: maas_url = rack_controller.url return urlparse(maas_url).hostname
def run(): """Run the maas-regiond master service. Spawns children workers up to the number of CPU's minimum is 4 workers. """ args = parse() # Remove all the command line arguments, so they don't interfere with # the twistd argument parser. sys.argv = sys.argv[:1] # Workers are spawned with environment so it knows that it would only # be a worker. if os.environ.get("MAAS_REGIOND_PROCESS_MODE") == "worker": # Ignore interrupt on the workers. The master will kill them directly. signal.signal(signal.SIGINT, signal.SIG_IGN) runWorkerServices() return # Circular imports. from maasserver.workers import set_max_workers_count # Debug mode, run the all-in-one mode. if args.debug: set_max_workers_count(1) runAllInOneServices() return # Calculate the number of workers. worker_count = args.workers if not worker_count: from maasserver.config import RegionConfiguration try: with RegionConfiguration.open() as config: worker_count = config.num_workers except Exception: worker_count = 4 if worker_count <= 0: raise ValueError("Number of workers must be greater than zero.") # Set the maximum number of workers. set_max_workers_count(worker_count) # Start the master services, which will spawn the required workers. runMasterServices()
def test__options_are_reset(self): self.useFixture(RegionConfigurationFixture()) with RegionConfiguration.open_for_update() as configuration: # Give the option a random value. if isinstance(getattr(configuration, self.option), str): value = factory.make_name("foobar") else: value = factory.pick_port() setattr(configuration, self.option, value) stdio = call_reset(**{self.option: True}) # Nothing is echoed back to the user. self.assertThat(stdio.getOutput(), Equals("")) self.assertThat(stdio.getError(), Equals("")) # There is no custom value in the configuration file. with open(RegionConfiguration.DEFAULT_FILENAME, "rb") as fd: settings = yaml.safe_load(fd) self.assertThat(settings, Equals({}))
def get_maas_facing_server_host(rack_controller=None, default_region_ip=None): """Return configured MAAS server hostname, for use by nodes or workers. :param rack_controller: The `RackController` from the point of view of which the server host should be computed. :param default_region_ip: The default source IP address to be used, if a specific URL is not defined. :return: Hostname or IP address, as configured in the MAAS URL config setting or as configured on rack_controller.url. """ if rack_controller is None or not rack_controller.url: if default_region_ip is not None: return default_region_ip with RegionConfiguration.open() as config: maas_url = config.maas_url else: maas_url = rack_controller.url return urlparse(maas_url).hostname
def test__set_and_get(self): config = RegionConfiguration({}) if isinstance(getattr(config, self.option), str): example_value = factory.make_name(self.option) elif self.option == "database_keepalive": example_value = random.choice([True, False]) else: example_value = factory.pick_port() # Argument values will most often be passed in from the command-line, # so convert to a string before use to reflect that usage. setattr(config, self.option, str(example_value)) self.assertEqual(example_value, getattr(config, self.option)) # It's also stored in the configuration database. expected_value = example_value if example_value == "true": expected_value = True elif expected_value == "false": expected_value = False self.assertEqual({self.option: expected_value}, config.store)
def test_dns_config_has_NS_record(self): self.patch(settings, "DNS_CONNECT", True) ip = factory.make_ipv4_address() with RegionConfiguration.open_for_update() as config: config.maas_url = "http://%s/" % ip domain = factory.make_Domain() node, static = self.create_node_with_static_ip(domain=domain) dns_update_all_zones() # Creating the domain triggered writing the zone file and updating the # DNS. self.dns_wait_soa(domain.name) # Get the NS record for the zone 'domain.name'. ns_record = dig_call(port=self.bind.config.port, commands=[domain.name, "NS", "+short"]) self.assertGreater(len(ns_record), 0, "No NS record for domain.name.") # Resolve that hostname. self.dns_wait_soa(ns_record) ip_of_ns_record = dig_call(port=self.bind.config.port, commands=[ns_record, "+short"]) self.assertEqual(ip, ip_of_ns_record)
def global_options(request): version = get_maas_version_ui() uuid = RegionController.objects.get_or_create_uuid() with RegionConfiguration.open() as config: maas_url = config.maas_url configs = Config.objects.get_configs([ "maas_name", "enable_analytics", "rpc_shared_secret", "completed_intro", ]) user_completed_intro = False completed_intro = configs["completed_intro"] if not hasattr(request, "user"): return {} if hasattr(request.user, "userprofile"): user_completed_intro = request.user.userprofile.completed_intro if not completed_intro and not request.user.is_superuser: # Only administrators can completed the main intro, normal users # cannot complete it so to them it has been done. completed_intro = True if request.user.is_authenticated: analytics_user_id = "%s-user%d" % (uuid, request.user.id) else: analytics_user_id = "%s-anon" % uuid return { "global_options": { "site_name": configs["maas_name"], "enable_analytics": configs["enable_analytics"], }, "debug": settings.DEBUG, "version": version, "files_version": version.replace(" ", ""), "doc_version": get_maas_doc_version(), "register_url": maas_url, "register_secret": configs["rpc_shared_secret"], "completed_intro": completed_intro, "user_completed_intro": user_completed_intro, "analytics_user_id": analytics_user_id, "maas_uuid": uuid, }
def global_options(request): version = get_maas_version_ui() with RegionConfiguration.open() as config: maas_url = config.maas_url user_completed_intro = False if hasattr(request.user, 'userprofile'): user_completed_intro = request.user.userprofile.completed_intro return { 'global_options': { 'site_name': Config.objects.get_config('maas_name'), 'enable_analytics': Config.objects.get_config('enable_analytics'), }, 'debug': settings.DEBUG, 'version': version, 'files_version': version.replace(" ", ""), 'doc_version': get_maas_doc_version(), 'register_url': maas_url, 'register_secret': Config.objects.get_config('rpc_shared_secret'), 'completed_intro': Config.objects.get_config('completed_intro'), 'user_completed_intro': user_completed_intro, }
def global_options(request): version = get_maas_version_ui() uuid = RegionController.objects.get_or_create_uuid() with RegionConfiguration.open() as config: maas_url = config.maas_url configs = Config.objects.get_configs([ 'maas_name', 'enable_analytics', 'rpc_shared_secret', 'completed_intro' ]) user_completed_intro = False completed_intro = configs['completed_intro'] if not hasattr(request, 'user'): return {} if hasattr(request.user, 'userprofile'): user_completed_intro = request.user.userprofile.completed_intro if not completed_intro and not request.user.is_superuser: # Only administrators can completed the main intro, normal users # cannot complete it so to them it has been done. completed_intro = True if request.user.is_authenticated: analytics_user_id = '%s-user%d' % (uuid, request.user.id) else: analytics_user_id = '%s-anon' % uuid return { 'global_options': { 'site_name': configs['maas_name'], 'enable_analytics': configs['enable_analytics'], }, 'debug': settings.DEBUG, 'version': version, 'files_version': version.replace(" ", ""), 'doc_version': get_maas_doc_version(), 'register_url': maas_url, 'register_secret': configs['rpc_shared_secret'], 'completed_intro': completed_intro, 'user_completed_intro': user_completed_intro, 'analytics_user_id': analytics_user_id, 'maas_uuid': uuid }
def test__default(self): config = RegionConfiguration({}) self.assertEqual(self.default, getattr(config, self.option))
def test_set_maas_url_rejects_bare_ipv6_addresses(self): config = RegionConfiguration({}) example_url = factory.make_simple_http_url( netloc=factory.make_ipv6_address()) with ExpectedException(formencode.api.Invalid): config.maas_url = example_url
def test_set_maas_url_accepts_hostnames(self): config = RegionConfiguration({}) example_url = factory.make_simple_http_url() config.maas_url = example_url self.assertEqual(example_url, config.maas_url) self.assertEqual({"maas_url": example_url}, config.store)
def test_default_database_conn_max_age(self): config = RegionConfiguration({}) self.assertEqual(60 * 5, config.database_conn_max_age)