def ask(self): # If we are a control node, skip this question. role = config_get(Role.config_key) if role == 'control': ip = config_get(ControlIp.config_key) config_set(**{self.config_key: ip}) return return super().ask()
def generate_selfsigned(): """Generate a self-signed certificate with associated keys. The certificate will have a fake CNAME and subjAltName since the expectation is that this certificate will only be used by clients that know its fingerprint and will not use a validation via a CA certificate and hostname. This approach is similar to Certificate Pinning, however, here a certificate is not embedded into the application but is generated on initialization at one node and its fingerprint is copied in a token to another node via a secure channel. https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning """ cert_path, key_path = ( Path(shell.config_get('config.cluster.tls-cert-path')), Path(shell.config_get('config.cluster.tls-key-path')), ) # Do not generate a new certificate and key if there is already an existing # pair. TODO: improve this check and allow renewal. if cert_path.exists() and key_path.exists(): return dummy_cn = 'microstack.run' key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend(), ) common_name = x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, dummy_cn)]) san = x509.SubjectAlternativeName([x509.DNSName(dummy_cn)]) basic_contraints = x509.BasicConstraints(ca=True, path_length=0) now = datetime.utcnow() cert = (x509.CertificateBuilder().subject_name(common_name).issuer_name( common_name).public_key(key.public_key()).serial_number( x509.random_serial_number()).not_valid_before(now).not_valid_after( now + relativedelta(years=10)).add_extension( basic_contraints, False).add_extension(san, False).sign(key, hashes.SHA256(), default_backend())) cert_fprint = cert.fingerprint(hashes.SHA256()).hex() shell.config_set(**{'config.cluster.fingerprint': cert_fprint}) serialized_cert = cert.public_bytes(encoding=serialization.Encoding.PEM) serialized_key = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) cert_path.write_bytes(serialized_cert) key_path.write_bytes(serialized_key)
def yes(self, answer: str) -> None: log.info('Configuring the Placement service...') if not call('openstack', 'user', 'show', 'placement'): check( 'openstack', 'user', 'create', '--domain', 'default', '--password', shell.config_get('config.credentials.placement-password'), 'placement', ) check('openstack', 'role', 'add', '--project', 'service', '--user', 'placement', 'admin') if not call('openstack', 'service', 'show', 'placement'): check('openstack', 'service', 'create', '--name', 'placement', '--description', '"Placement API"', 'placement') for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'placement', endpoint, 'http://{control_ip}:8778'.format(**_env)) log.info('Running Placement DB migrations...') check('snap-openstack', 'launch', 'placement-manage', 'db', 'sync') enable('placement-uwsgi')
def ask(self): # Skip this question for a compute node since the control IP # address is taken from the connection string instead. role = config_get(Role.config_key) if role == 'compute': return return super().ask()
def yes(self, answer: str) -> None: log.info('Configuring the Cinder services...') if not call('openstack', 'user', 'show', 'cinder'): check('openstack', 'user', 'create', '--domain', 'default', '--password', shell.config_get('config.credentials.cinder-password'), 'cinder') check('openstack', 'role', 'add', '--project', 'service', '--user', 'cinder', 'admin') control_ip = _env['control_ip'] for endpoint in ['public', 'internal', 'admin']: for api_version in ['v2', 'v3']: if not call('openstack', 'service', 'show', f'cinder{api_version}'): check('openstack', 'service', 'create', '--name', f'cinder{api_version}', '--description', f'"Cinder {api_version} API"', f'volume{api_version}') if not check_output('openstack', 'endpoint', 'list', '--service', f'volume{api_version}', '--interface', endpoint): check( 'openstack', 'endpoint', 'create', '--region', 'microstack', f'volume{api_version}', endpoint, f'http://{control_ip}:8776/{api_version}/' '$(project_id)s') log.info('Running Cinder DB migrations...') check('snap-openstack', 'launch', 'cinder-manage', 'db', 'sync') enable('cinder-uwsgi') enable('cinder-scheduler')
def ask(self): # Skip this question for a control node since we are not connecting # to ourselves. role = config_get(Role.config_key) if role == 'control': return return super().ask()
def yes(self, answer: str) -> None: log.info('Configuring Glance ...') if not call('openstack', 'user', 'show', 'glance'): check('openstack', 'user', 'create', '--domain', 'default', '--password', shell.config_get('config.credentials.glance-password'), 'glance') check('openstack', 'role', 'add', '--project', 'service', '--user', 'glance', 'admin') if not call('openstack', 'service', 'show', 'image'): check('openstack', 'service', 'create', '--name', 'glance', '--description', '"OpenStack Image"', 'image') for endpoint in ['internal', 'admin', 'public']: check('openstack', 'endpoint', 'create', '--region', 'microstack', 'image', endpoint, 'http://{compute_ip}:9292'.format(**_env)) check('snap-openstack', 'launch', 'glance-manage', 'db_sync') # TODO: remove the glance registry # https://blueprints.launchpad.net/glance/+spec/deprecate-registry for service in [ 'glance-api', 'registry', ]: enable(service) nc_wait(_env['compute_ip'], '9292') sleep(5) # TODO: log_wait self._fetch_cirros()
def yes(self, answer): clustered = config_get('config.is-clustered') if not clustered: ip_dict = { 'config.network.control-ip': answer, 'config.network.compute-ip': answer, } config_set(**ip_dict) _env.update(ip_dict) else: ip_dict = config_get( *['config.network.control-ip', 'config.network.compute-ip']) _env.update({ 'control_ip': ip_dict['config.network.control-ip'], 'compute_ip': ip_dict['config.network.compute-ip'], })
def yes(self, answer: str) -> None: log.info('Configuring nova control plane services ...') if not call('openstack', 'user', 'show', 'nova'): check('openstack', 'user', 'create', '--domain', 'default', '--password', shell.config_get('config.credentials.nova-password'), 'nova') check('openstack', 'role', 'add', '--project', 'service', '--user', 'nova', 'admin') # Assign the reader role to the nova user so that read-only # application credentials can be created. check('openstack', 'role', 'add', '--project', 'service', '--user', 'nova', 'reader') log.info('Running Nova API DB migrations' ' (this may take a lot of time)...') check('snap-openstack', 'launch', 'nova-manage', 'api_db', 'sync') if 'cell0' not in check_output('snap-openstack', 'launch', 'nova-manage', 'cell_v2', 'list_cells'): check('snap-openstack', 'launch', 'nova-manage', 'cell_v2', 'map_cell0') if 'cell1' not in check_output('snap-openstack', 'launch', 'nova-manage', 'cell_v2', 'list_cells'): check('snap-openstack', 'launch', 'nova-manage', 'cell_v2', 'create_cell', '--name=cell1', '--verbose') log.info('Running Nova DB migrations' ' (this may take a lot of time)...') check('snap-openstack', 'launch', 'nova-manage', 'db', 'sync') enable('nova-api') restart('nova-compute') for service in [ 'nova-api-metadata', 'nova-conductor', 'nova-scheduler', ]: enable(service) nc_wait(_env['compute_ip'], '8774') sleep(5) # TODO: log_wait if not call('openstack', 'service', 'show', 'compute'): check('openstack', 'service', 'create', '--name', 'nova', '--description', '"Openstack Compute"', 'compute') for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'compute', endpoint, 'http://{control_ip}:8774/v2.1'.format(**_env)) log.info('Creating default flavors...') self._flavors()
def yes(self, answer: bool): log.info('Configuring clustering ...') role_question = clustering.Role() if not (self.interactive and self.role_interactive): role_question.interactive = False role_question.ask() questions = [ # Skipped for the compute role and is automatically taken # from the connection string. clustering.ControlIp(), # Skipped for the control role since it is identical to the # control node IP. clustering.ComputeIp(), ] for question in questions: if not self.interactive: question.interactive = False question.ask() connection_string_question = clustering.ConnectionString() if not (self.interactive and self.connection_string_interactive): connection_string_question.interactive = False connection_string_question.ask() role = shell.config_get('config.cluster.role') if role == 'compute': log.info('Setting up as a compute node.') # Gets config info and sets local env vals. check_output('microstack_join') shell.config_set( **{ 'config.services.control-plane': 'false', 'config.services.hypervisor': 'true', }) if role == 'control': log.info('Setting up as a control node.') shell.config_set( **{ 'config.services.control-plane': 'true', 'config.services.hypervisor': 'true', }) # Generate a self-signed certificate for the clustering service. cluster_tls.generate_selfsigned() # Write templates check('snap-openstack', 'setup')
def yes(self, answer: str) -> None: log.info('Configuring Neutron') if not call('openstack', 'user', 'show', 'neutron'): check('openstack', 'user', 'create', '--domain', 'default', '--password', shell.config_get('config.credentials.neutron-password'), 'neutron') check('openstack', 'role', 'add', '--project', 'service', '--user', 'neutron', 'admin') if not call('openstack', 'service', 'show', 'network'): check('openstack', 'service', 'create', '--name', 'neutron', '--description', '"OpenStack Network"', 'network') for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'network', endpoint, 'http://{control_ip}:9696'.format(**_env)) check('snap-openstack', 'launch', 'neutron-db-manage', 'upgrade', 'head') enable('neutron-api') enable('neutron-ovn-metadata-agent') nc_wait(_env['control_ip'], '9696') sleep(5) # TODO: log_wait if not call('openstack', 'network', 'show', 'test'): check('openstack', 'network', 'create', 'test') if not call('openstack', 'subnet', 'show', 'test-subnet'): check('openstack', 'subnet', 'create', '--network', 'test', '--subnet-range', '192.168.222.0/24', 'test-subnet') if not call('openstack', 'network', 'show', 'external'): check('openstack', 'network', 'create', '--external', '--provider-physical-network=physnet1', '--provider-network-type=flat', 'external') if not call('openstack', 'subnet', 'show', 'external-subnet'): check('openstack', 'subnet', 'create', '--network', 'external', '--subnet-range', _env['extcidr'], '--no-dhcp', 'external-subnet') if not call('openstack', 'router', 'show', 'test-router'): check('openstack', 'router', 'create', 'test-router') check('openstack', 'router', 'add', 'subnet', 'test-router', 'test-subnet') check('openstack', 'router', 'set', '--external-gateway', 'external', 'test-router')
def _create_dbs(self) -> None: db_creds = shell.config_get('config.credentials') for service_user, db_name in (('neutron', 'neutron'), ('nova', 'nova'), ('nova', 'nova_api'), ('nova', 'nova_cell0'), ('cinder', 'cinder'), ('glance', 'glance'), ('keystone', 'keystone'), ('placement', 'placement')): db_password = db_creds[f'{service_user}-password'] sql("CREATE USER IF NOT EXISTS '{user}'@'%'" " IDENTIFIED BY '{db_password}';".format( user=service_user, db_password=db_password, **_env)) sql("CREATE DATABASE IF NOT EXISTS `{db}`;".format(db=db_name)) sql("GRANT ALL PRIVILEGES ON {db}.* TO '{user}'@'%';" "".format(db=db_name, user=service_user, **_env))
def yes(self, answer: str) -> None: log.info('restarting libvirt and virtlogd ...') # This fixes an issue w/ logging not getting set. # TODO: fix issue. restart('libvirtd') restart('virtlogd') restart('nova-compute') role = shell.config_get('config.cluster.role') if role == 'control': # TODO: since snap-openstack launch is used, this depends on the # database readiness and hence the clustering service is enabled # and started here. There needs to be a better way to do this. enable('cluster-uwsgi') enable('horizon-uwsgi') check('snapctl', 'set', 'initialized=true') log.info('Complete. Marked microstack as initialized!')
def _setup_secrets(): # If a user runs init multiple times we do not want to generate # new credentials to keep the init operation idempotent. existing_creds = shell.config_get('config.credentials') if isinstance(existing_creds, dict): existing_cred_keys = existing_creds.keys() else: existing_cred_keys = [] shell.config_set( **{ f'config.credentials.{k}': credentials.generate_password() for k in [ 'mysql-root-password', 'rabbitmq-password', 'keystone-password', 'nova-password', 'cinder-password', 'neutron-password', 'placement-password', 'glance-password', 'ovn-metadata-proxy-shared-secret', ] if k not in existing_cred_keys })
def _load(self): role = config_get(Role.config_key) if role == 'compute': return fetch_ip_address() or super().load() return super()._load()
def _load(self): role = config_get(Role.config_key) if role == 'compute': return default_source_address() or super().load() return super()._load()
def _load(self): if config_get(Role.config_key) == 'control': return default_source_address() or super()._load() return super()._load()
def _load(self): if config_get(Role.config_key) == 'control': return fetch_ip_address() or super()._load() return super()._load()