class Controller(rest.RestController): """Version 1 API controller root.""" isystems = system.SystemController() ihosts = host.HostController() helm_charts = helm_charts.HelmChartsController() inodes = node.NodeController() icpus = cpu.CPUController() imemorys = memory.MemoryController() iinterfaces = interface.InterfaceController() ports = port.PortController() ethernet_ports = ethernet_port.EthernetPortController() istors = storage.StorageController() ilvgs = lvg.LVGController() ipvs = pv.PVController() idisks = disk.DiskController() partitions = partition.PartitionController() iprofile = profile.ProfileController() itrapdest = trapdest.TrapDestController() icommunity = community.CommunityController() iuser = user.UserController() idns = dns.DNSController() intp = ntp.NTPController() ptp = ptp.PTPController() iextoam = network_oam.OAMNetworkController() controller_fs = controller_fs.ControllerFsController() storage_backend = storage_backend.StorageBackendController() storage_lvm = storage_lvm.StorageLVMController() storage_file = storage_file.StorageFileController() storage_external = storage_external.StorageExternalController() storage_ceph = storage_ceph.StorageCephController() storage_tiers = storage_tier.StorageTierController() storage_ceph_external = \ storage_ceph_external.StorageCephExternalController() ceph_mon = ceph_mon.CephMonController() drbdconfig = drbdconfig.drbdconfigsController() addresses = address.AddressController() addrpools = address_pool.AddressPoolController() routes = route.RouteController() certificate = certificate.CertificateController() isensors = sensor.SensorController() isensorgroups = sensorgroup.SensorGroupController() loads = load.LoadController() pci_devices = pci_device.PCIDeviceController() upgrade = upgrade.UpgradeController() networks = network.NetworkController() interface_networks = interface_network.InterfaceNetworkController() service_parameter = service_parameter.ServiceParameterController() clusters = cluster.ClusterController() lldp_agents = lldp_agent.LLDPAgentController() lldp_neighbours = lldp_neighbour.LLDPNeighbourController() services = service.SMServiceController() servicenodes = servicenode.SMServiceNodeController() servicegroup = servicegroup.SMServiceGroupController() health = health.HealthController() registry_image = registry_image.RegistryImageController() remotelogging = remotelogging.RemoteLoggingController() sdn_controller = sdn_controller.SDNControllerController() license = license.LicenseController() labels = label.LabelController() fernet_repo = fernet_repo.FernetKeyController() apps = kube_app.KubeAppController() datanetworks = datanetwork.DataNetworkController() interface_datanetworks = interface_datanetwork.InterfaceDataNetworkController() host_fs = host_fs.HostFsController() @wsme_pecan.wsexpose(V1) def get(self): # NOTE: The reason why convert() it's being called for every # request is because we need to get the host url from # the request object to make the links. return V1.convert()
class SystemController(rest.RestController): """REST controller for isystem.""" ihosts = host.HostController(from_isystem=True) "Expose ihosts as a sub-element of isystem" controller_fs = controllerfs.ControllerFsController() "Expose controller_fs as a sub-element of isystem" _custom_actions = { 'detail': ['GET'], 'mgmtvlan': ['GET'], } def __init__(self): self._bm_region = None def bm_region_get(self): if not self._bm_region: networks = pecan.request.dbapi.networks_get_by_type( constants.NETWORK_TYPE_BM) if networks: self._bm_region = constants.REGION_PRIMARY else: networks = pecan.request.dbapi.networks_get_by_type( constants.NETWORK_TYPE_MGMT) # During initial system install no networks assigned yet if networks: self._bm_region = constants.REGION_SECONDARY return self._bm_region def _get_updates(self, patch): """Retrieve the updated attributes from the patch request.""" updates = {} for p in patch: attribute = p['path'] if p['path'][0] != '/' else p['path'][1:] updates[attribute] = p['value'] return updates def _verify_sdn_disabled(self): # Check if SDN controller is configured sdn_controllers = pecan.request.dbapi.sdn_controller_get_list() if sdn_controllers: msg = _("SDN cannot be disabled when SDN controller is " "configured.") raise wsme.exc.ClientSideError(msg) # Check if SDN Controller service parameters neutron_parameters = [] for section in [ constants.SERVICE_PARAM_SECTION_NETWORK_ML2, constants.SERVICE_PARAM_SECTION_NETWORK_ML2_ODL, constants.SERVICE_PARAM_SECTION_NETWORK_DEFAULT ]: try: parm_list = pecan.request.dbapi.service_parameter_get_all( service=constants.SERVICE_TYPE_NETWORK, section=section) neutron_parameters = neutron_parameters + parm_list except NoResultFound: continue if neutron_parameters: msg = _("SDN cannot be disabled when SDN service parameters " "are configured.") raise wsme.exc.ClientSideError(msg) def _verify_sdn_enabled(self): # If SDN is enabled then OAM and Management network # must belong to the same Address Family oam_network = pecan.request.dbapi.network_get_by_type( constants.NETWORK_TYPE_OAM) oam_address_pool = pecan.request.dbapi.address_pool_get( oam_network.pool_uuid) oam_ip_version = oam_address_pool.family mgmt_network = pecan.request.dbapi.network_get_by_type( constants.NETWORK_TYPE_MGMT) mgmt_address_pool = pecan.request.dbapi.address_pool_get( mgmt_network.pool_uuid) mgmt_ip_version = mgmt_address_pool.family if oam_ip_version != mgmt_ip_version: msg = _("Invalid network address - OAM and Management Network IP" " Families must be the same when SDN is enabled.") raise wsme.exc.ClientSideError(msg) def _check_hosts(self): hosts = pecan.request.dbapi.ihost_get_list() for h in hosts: if api_utils.is_aio_simplex_host_unlocked(h): raise wsme.exc.ClientSideError( _("Host {} must be locked.".format(h['hostname']))) elif (h['administrative'] != constants.ADMIN_LOCKED and constants.WORKER in h['subfunctions'] and not api_utils.is_host_active_controller(h) and not api_utils.is_host_simplex_controller(h)): raise wsme.exc.ClientSideError( _("Host {} must be locked.".format(h['hostname']))) def _get_isystem_collection(self, marker, limit, sort_key, sort_dir, expand=False, resource_url=None): limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.system.get_by_uuid(pecan.request.context, marker) isystem = pecan.request.dbapi.isystem_get_list(limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) for i in isystem: i.capabilities['bm_region'] = self.bm_region_get() return SystemCollection.convert_with_links(isystem, limit, url=resource_url, expand=expand, sort_key=sort_key, sort_dir=sort_dir) @wsme_pecan.wsexpose(SystemCollection, types.uuid, int, wtypes.text, wtypes.text) def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of isystems. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ return self._get_isystem_collection(marker, limit, sort_key, sort_dir) @wsme_pecan.wsexpose(SystemCollection, types.uuid, int, wtypes.text, wtypes.text) def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of isystem with detail. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ # /detail should only work agaist collections parent = pecan.request.path.split('/')[:-1][-1] if parent != "isystem": raise exception.HTTPNotFound expand = True resource_url = '/'.join(['isystem', 'detail']) return self._get_isystem_collection(marker, limit, sort_key, sort_dir, expand, resource_url) @wsme_pecan.wsexpose(System, types.uuid) def get_one(self, isystem_uuid): """Retrieve information about the given isystem. :param isystem_uuid: UUID of a isystem. """ rpc_isystem = objects.system.get_by_uuid(pecan.request.context, isystem_uuid) rpc_isystem.capabilities['bm_region'] = self.bm_region_get() return System.convert_with_links(rpc_isystem) @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(System, body=System) def post(self, isystem): """Create a new system.""" raise exception.OperationNotPermitted @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(System, types.uuid, body=[six.text_type]) def patch(self, isystem_uuid, patch): """Update an existing isystem. :param isystem_uuid: UUID of a isystem. :param patch: a json PATCH document to apply to this isystem. """ rpc_isystem = objects.system.get_by_uuid(pecan.request.context, isystem_uuid) system_dict = rpc_isystem.as_dict() updates = self._get_updates(patch) change_https = False change_sdn = False change_dc_role = False vswitch_type = None # prevent description field from being updated for p in jsonpatch.JsonPatch(patch): if p['path'] == '/software_version': raise wsme.exc.ClientSideError( _("software_version field " "cannot be modified.")) if p['path'] == '/system_type': if rpc_isystem is not None: if rpc_isystem.system_type is not None: raise wsme.exc.ClientSideError( _("system_type field " "cannot be " "modified.")) if (p['path'] == '/system_mode' and p.get('value') != rpc_isystem.system_mode): if rpc_isystem is not None and \ rpc_isystem.system_mode is not None: if rpc_isystem.system_type != constants.TIS_AIO_BUILD: raise wsme.exc.ClientSideError( "system_mode can only be modified on an " "AIO system") system_mode_options = [ constants.SYSTEM_MODE_DUPLEX, constants.SYSTEM_MODE_DUPLEX_DIRECT ] new_system_mode = p['value'] if rpc_isystem.system_mode == \ constants.SYSTEM_MODE_SIMPLEX: msg = _("Cannot modify system mode when it is " "already set to %s." % rpc_isystem.system_mode) raise wsme.exc.ClientSideError(msg) elif new_system_mode == constants.SYSTEM_MODE_SIMPLEX: msg = _("Cannot modify system mode to simplex when " "it is set to %s " % rpc_isystem.system_mode) raise wsme.exc.ClientSideError(msg) if new_system_mode not in system_mode_options: raise wsme.exc.ClientSideError( "Invalid value for system_mode, it can only" " be modified to '%s' or '%s'" % (constants.SYSTEM_MODE_DUPLEX, constants.SYSTEM_MODE_DUPLEX_DIRECT)) if p['path'] == '/timezone': timezone = p['value'] if not os.path.isfile("/usr/share/zoneinfo/%s" % timezone): raise wsme.exc.ClientSideError( _("Timezone file %s " "does not exist." % timezone)) if p['path'] == '/sdn_enabled': sdn_enabled = p['value'].lower() patch.remove(p) if p['path'] == '/https_enabled': https_enabled = p['value'].lower() patch.remove(p) if p['path'] == '/distributed_cloud_role': distributed_cloud_role = p['value'] patch.remove(p) if p['path'] == '/vswitch_type': vswitch_type = p['value'] patch.remove(p) if p['path'] == '/security_feature': security_feature = p['value'] patch.remove(p) try: patched_system = jsonpatch.apply_patch(system_dict, jsonpatch.JsonPatch(patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) if 'sdn_enabled' in updates: if sdn_enabled != rpc_isystem['capabilities']['sdn_enabled']: self._check_hosts() change_sdn = True if sdn_enabled == 'true': self._verify_sdn_enabled() patched_system['capabilities']['sdn_enabled'] = True else: self._verify_sdn_disabled() patched_system['capabilities']['sdn_enabled'] = False if 'https_enabled' in updates: if https_enabled != rpc_isystem['capabilities']['https_enabled']: change_https = True if https_enabled == 'true': patched_system['capabilities']['https_enabled'] = True else: patched_system['capabilities']['https_enabled'] = False else: raise wsme.exc.ClientSideError( _("https_enabled is already set" " as %s" % https_enabled)) if 'distributed_cloud_role' in updates: # At this point dc role cannot be changed after config_controller # and config_subcloud if rpc_isystem['distributed_cloud_role'] is None and \ distributed_cloud_role in \ [constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD]: change_dc_role = True patched_system[ 'distributed_cloud_role'] = distributed_cloud_role else: raise wsme.exc.ClientSideError( _("distributed_cloud_role is already set " " as %s" % rpc_isystem['distributed_cloud_role'])) if 'vswitch_type' in updates: if vswitch_type == rpc_isystem['capabilities']['vswitch_type']: raise wsme.exc.ClientSideError( _("vswitch_type is already set" " as %s" % vswitch_type)) patched_system['capabilities']['vswitch_type'] = vswitch_type if 'security_feature' in updates: # Security feature string must be translated from user values to # kernel options if (security_feature in constants.SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_OPTS): security_feature_value = \ constants.SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_OPTS[security_feature] patched_system['security_feature'] = security_feature_value else: raise wsme.exc.ClientSideError( _("Unexpected value %s specified for " "security_feature" % security_feature)) # Update only the fields that have changed name = "" contact = "" location = "" system_mode = "" timezone = "" capabilities = {} distributed_cloud_role = "" security_feature = "" for field in objects.system.fields: if rpc_isystem[field] != patched_system[field]: rpc_isystem[field] = patched_system[field] if field == 'name': name = rpc_isystem[field] if field == 'contact': contact = rpc_isystem[field] if field == 'location': location = rpc_isystem[field] if field == 'system_mode': system_mode = rpc_isystem[field] if field == 'timezone': timezone = rpc_isystem[field] if field == 'capabilities': capabilities = rpc_isystem[field] if field == 'distributed_cloud_role': distributed_cloud_role = rpc_isystem[field] if field == 'security_feature': security_feature = rpc_isystem[field] delta = rpc_isystem.obj_what_changed() delta_handle = list(delta) rpc_isystem.save() if name: LOG.info("update system name") pecan.request.rpcapi.configure_isystemname(pecan.request.context, name) if name or location or contact: LOG.info("update SNMP config") pecan.request.rpcapi.update_snmp_config(pecan.request.context) if 'system_mode' in delta_handle: LOG.info("update system mode %s" % system_mode) pecan.request.rpcapi.update_system_mode_config( pecan.request.context) if timezone: LOG.info("update system timezone to %s" % timezone) pecan.request.rpcapi.configure_system_timezone( pecan.request.context) if capabilities: if change_sdn: LOG.info("update sdn to %s" % capabilities) pecan.request.rpcapi.update_sdn_enabled(pecan.request.context) if change_https: LOG.info("update https to %s" % capabilities) pecan.request.rpcapi.configure_system_https( pecan.request.context) if vswitch_type: LOG.info("update vswitch_type to %s" % capabilities) pecan.request.rpcapi.update_vswitch_type(pecan.request.context) if distributed_cloud_role and change_dc_role: LOG.info("update distributed cloud role to %s" % distributed_cloud_role) pecan.request.rpcapi.update_distributed_cloud_role( pecan.request.context) if 'security_feature' in delta_handle: LOG.info("update security_feature %s" % security_feature) pecan.request.rpcapi.update_security_feature_config( pecan.request.context) return System.convert_with_links(rpc_isystem) @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(None, types.uuid, status_code=204) def delete(self, isystem_uuid): """Delete a isystem. :param isystem_uuid: UUID of a isystem. """ raise exception.OperationNotPermitted @wsme_pecan.wsexpose(int) def mgmtvlan(self): local_hostname = cutils.get_local_controller_hostname() controller = pecan.request.dbapi.ihost_get(local_hostname) host_id = controller['id'] interface_list = pecan.request.dbapi.iinterface_get_by_ihost(host_id) for interface in interface_list: for network_id in interface['networks']: network = pecan.request.dbapi.network_get_by_id(network_id) if network.type == constants.NETWORK_TYPE_MGMT: if 'vlan_id' not in interface: return 0 else: return interface['vlan_id'] return None
class SystemController(rest.RestController): """REST controller for isystem.""" ihosts = host.HostController(from_isystem=True) "Expose ihosts as a sub-element of isystem" controller_fs = controllerfs.ControllerFsController() "Expose controller_fs as a sub-element of isystem" _custom_actions = { 'detail': ['GET'], 'mgmtvlan': ['GET'], } def __init__(self): self._bm_region = None self._kube_op = sys_kube.KubeOperator() def bm_region_get(self): if not self._bm_region: networks = pecan.request.dbapi.networks_get_by_type( constants.NETWORK_TYPE_BM) if networks: self._bm_region = constants.REGION_PRIMARY else: networks = pecan.request.dbapi.networks_get_by_type( constants.NETWORK_TYPE_MGMT) # During initial system install no networks assigned yet if networks: self._bm_region = constants.REGION_SECONDARY return self._bm_region def _get_updates(self, patch): """Retrieve the updated attributes from the patch request.""" updates = {} for p in patch: attribute = p['path'] if p['path'][0] != '/' else p['path'][1:] updates[attribute] = p['value'] return updates def _verify_sdn_disabled(self): # Check if SDN controller is configured sdn_controllers = pecan.request.dbapi.sdn_controller_get_list() if sdn_controllers: msg = _("SDN cannot be disabled when SDN controller is " "configured.") raise wsme.exc.ClientSideError(msg) def _verify_sdn_enabled(self): # If SDN is enabled then OAM and Management network # must belong to the same Address Family oam_network = pecan.request.dbapi.network_get_by_type( constants.NETWORK_TYPE_OAM) oam_address_pool = pecan.request.dbapi.address_pool_get( oam_network.pool_uuid) oam_ip_version = oam_address_pool.family mgmt_network = pecan.request.dbapi.network_get_by_type( constants.NETWORK_TYPE_MGMT) mgmt_address_pool = pecan.request.dbapi.address_pool_get( mgmt_network.pool_uuid) mgmt_ip_version = mgmt_address_pool.family if oam_ip_version != mgmt_ip_version: msg = _("Invalid network address - OAM and Management Network IP" " Families must be the same when SDN is enabled.") raise wsme.exc.ClientSideError(msg) def _check_hosts(self): hosts = pecan.request.dbapi.ihost_get_list() for h in hosts: if api_utils.is_aio_simplex_host_unlocked(h): raise wsme.exc.ClientSideError( _("Host {} must be locked.".format(h['hostname']))) elif (h['administrative'] != constants.ADMIN_LOCKED and constants.WORKER in h['subfunctions'] and not api_utils.is_host_active_controller(h) and not api_utils.is_host_simplex_controller(h)): raise wsme.exc.ClientSideError( _("Host {} must be locked.".format(h['hostname']))) def _check_interfaces(self, system_mode): iinterfaces = pecan.request.dbapi.iinterface_get_all() mgmt_if = None cluster_host_if = None for iif in iinterfaces: if iif.networktypelist: if constants.NETWORK_TYPE_MGMT in iif.networktypelist: mgmt_if = iif if constants.NETWORK_TYPE_CLUSTER_HOST in iif.networktypelist: cluster_host_if = iif if mgmt_if and cluster_host_if: break if mgmt_if is None: msg = _("Cannot modify system mode to %s " "without configuring the management " "interface." % system_mode) raise wsme.exc.ClientSideError(msg) if mgmt_if.ifname == constants.LOOPBACK_IFNAME: msg = _("Cannot modify system mode to %s " "when the management interface is " "configured on loopback. " % system_mode) raise wsme.exc.ClientSideError(msg) if cluster_host_if is None: msg = _("Cannot modify system mode to %s " "without configuring the cluster-host " "interface." % system_mode) raise wsme.exc.ClientSideError(msg) if cluster_host_if.ifname == constants.LOOPBACK_IFNAME: msg = _("Cannot modify system mode to %s " "when the cluster-host interface is " "configured on loopback. " % system_mode) raise wsme.exc.ClientSideError(msg) def _check_controller_locked(self): controller = api_utils.HostHelper.get_active_controller() if controller is None: return if controller.administrative != constants.ADMIN_LOCKED: msg = _("Cannot modify system mode if host '%s' is not " "locked." % controller.hostname) raise wsme.exc.ClientSideError(msg) def _get_isystem_collection(self, marker, limit, sort_key, sort_dir, expand=False, resource_url=None): limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.system.get_by_uuid(pecan.request.context, marker) isystem = pecan.request.dbapi.isystem_get_list(limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) for i in isystem: i.capabilities['bm_region'] = self.bm_region_get() return SystemCollection.convert_with_links(isystem, limit, url=resource_url, expand=expand, sort_key=sort_key, sort_dir=sort_dir) @wsme_pecan.wsexpose(SystemCollection, types.uuid, int, wtypes.text, wtypes.text) def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of isystems. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ return self._get_isystem_collection(marker, limit, sort_key, sort_dir) @wsme_pecan.wsexpose(SystemCollection, types.uuid, int, wtypes.text, wtypes.text) def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of isystem with detail. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ # /detail should only work agaist collections parent = pecan.request.path.split('/')[:-1][-1] if parent != "isystem": raise exception.HTTPNotFound expand = True resource_url = '/'.join(['isystem', 'detail']) return self._get_isystem_collection(marker, limit, sort_key, sort_dir, expand, resource_url) @wsme_pecan.wsexpose(System, types.uuid) def get_one(self, isystem_uuid): """Retrieve information about the given isystem. :param isystem_uuid: UUID of a isystem. """ rpc_isystem = objects.system.get_by_uuid(pecan.request.context, isystem_uuid) rpc_isystem.capabilities['bm_region'] = self.bm_region_get() return System.convert_with_links(rpc_isystem) @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(System, body=System) def post(self, isystem): """Create a new system.""" raise exception.OperationNotPermitted @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(System, types.uuid, body=[six.text_type]) def patch(self, isystem_uuid, patch): """Update an existing isystem. :param isystem_uuid: UUID of a isystem. :param patch: a json PATCH document to apply to this isystem. """ rpc_isystem = objects.system.get_by_uuid(pecan.request.context, isystem_uuid) system_dict = rpc_isystem.as_dict() updates = self._get_updates(patch) change_https = False change_sdn = False change_dc_role = False vswitch_type = None new_system_mode = None # prevent description field from being updated for p in jsonpatch.JsonPatch(patch): if p['path'] == '/software_version': raise wsme.exc.ClientSideError(_("software_version field " "cannot be modified.")) if p['path'] == '/system_type': if rpc_isystem is not None: if rpc_isystem.system_type is not None: raise wsme.exc.ClientSideError(_("system_type field " "cannot be " "modified.")) if (p['path'] == '/system_mode' and p.get('value') != rpc_isystem.system_mode): if rpc_isystem is not None and \ rpc_isystem.system_mode is not None: if rpc_isystem.system_type != constants.TIS_AIO_BUILD: raise wsme.exc.ClientSideError( "system_mode can only be modified on an " "AIO system") system_mode_options = [constants.SYSTEM_MODE_DUPLEX, constants.SYSTEM_MODE_DUPLEX_DIRECT] new_system_mode = p['value'] # Allow modification to system mode during bootstrap. Once the # initial configuration is complete, this type of request will # be bound to the conditions below. if cutils.is_initial_config_complete(): if rpc_isystem.system_mode == \ constants.SYSTEM_MODE_DUPLEX: msg = _("Cannot modify system mode when it is " "set to %s." % rpc_isystem.system_mode) raise wsme.exc.ClientSideError(msg) elif new_system_mode != constants.SYSTEM_MODE_SIMPLEX: self._check_controller_locked() self._check_interfaces(new_system_mode) else: system_mode_options.append(constants.SYSTEM_MODE_SIMPLEX) if new_system_mode not in system_mode_options: raise wsme.exc.ClientSideError( "Invalid value for system_mode, it can only" " be modified to '%s' or '%s'" % (constants.SYSTEM_MODE_DUPLEX, constants.SYSTEM_MODE_DUPLEX_DIRECT)) if p['path'] == '/timezone': timezone = p['value'] if not os.path.isfile("/usr/share/zoneinfo/%s" % timezone): raise wsme.exc.ClientSideError(_("Timezone file %s " "does not exist." % timezone)) if (p['path'] == '/latitude' or p['path'] == '/longitude'): if p['value'] is not None: if len(p['value']) > 30: raise wsme.exc.ClientSideError("Geolocation coordinates can not be " "longer than 30 characters") if p['path'] == '/sdn_enabled': sdn_enabled = p['value'].lower() patch.remove(p) if p['path'] == '/https_enabled': https_enabled = p['value'].lower() patch.remove(p) if p['path'] == '/distributed_cloud_role': distributed_cloud_role = p['value'] patch.remove(p) if p['path'] == '/vswitch_type': vswitch_type = p['value'] patch.remove(p) if p['path'] == '/security_feature': security_feature = p['value'] patch.remove(p) try: patched_system = jsonpatch.apply_patch(system_dict, jsonpatch.JsonPatch(patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) if 'system_mode' in updates: # Update capabilities if system mode is changed from simplex to # duplex after the initial config is complete if (cutils.is_initial_config_complete() and rpc_isystem.system_mode == constants.SYSTEM_MODE_SIMPLEX and new_system_mode == constants.SYSTEM_MODE_DUPLEX): patched_system['capabilities']['simplex_to_duplex_migration'] = True if 'sdn_enabled' in updates: if sdn_enabled != rpc_isystem['capabilities']['sdn_enabled']: self._check_hosts() change_sdn = True if sdn_enabled == 'true': self._verify_sdn_enabled() patched_system['capabilities']['sdn_enabled'] = True else: self._verify_sdn_disabled() patched_system['capabilities']['sdn_enabled'] = False if 'https_enabled' in updates: # Pre-check: if user is setting https_enabled to false # while 'ssl' cert is managed by cert-manager, return error # (Otherwise, cert-mon will turn https back on during cert-renewal process) managed_by_cm = self._kube_op.kube_get_secret( constants.RESTAPI_CERT_SECRET_NAME, constants.CERT_NAMESPACE_PLATFORM_CERTS) if https_enabled == 'false' and managed_by_cm is not None: msg = "Certificate is currently being managed by cert-manager. " \ "Remove %s Certificate and Secret before disabling https." % \ constants.RESTAPI_CERT_SECRET_NAME raise wsme.exc.ClientSideError(_(msg)) if https_enabled != rpc_isystem['capabilities']['https_enabled']: change_https = True if https_enabled == 'true': patched_system['capabilities']['https_enabled'] = True else: patched_system['capabilities']['https_enabled'] = False else: raise wsme.exc.ClientSideError(_("https_enabled is already set" " as %s" % https_enabled)) if 'distributed_cloud_role' in updates: # At this point dc role cannot be changed after initial # configuration is complete if (rpc_isystem['distributed_cloud_role'] is not None and cutils.is_initial_config_complete()): raise wsme.exc.ClientSideError( _("distributed_cloud_role is already set " " as %s" % rpc_isystem['distributed_cloud_role'])) # allow set the role to None before the initial config # is complete elif ((distributed_cloud_role in [constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD] or distributed_cloud_role is None) and not cutils.is_initial_config_complete()): change_dc_role = True patched_system['distributed_cloud_role'] = distributed_cloud_role else: raise wsme.exc.ClientSideError(_("Unexpected value %s specified" " for distributed_cloud_role" % distributed_cloud_role)) if 'vswitch_type' in updates: if vswitch_type == rpc_isystem['capabilities']['vswitch_type']: raise wsme.exc.ClientSideError(_("vswitch_type is already set" " as %s" % vswitch_type)) patched_system['capabilities']['vswitch_type'] = vswitch_type if 'security_feature' in updates: # Security feature string must be translated from user values to # kernel options if (security_feature in constants.SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_OPTS): security_feature_value = \ constants.SYSTEM_SECURITY_FEATURE_SPECTRE_MELTDOWN_OPTS[security_feature] patched_system['security_feature'] = security_feature_value else: raise wsme.exc.ClientSideError(_("Unexpected value %s specified for " "security_feature" % security_feature)) # Update only the fields that have changed name = "" contact = "" location = "" system_mode = "" timezone = "" capabilities = {} distributed_cloud_role = "" security_feature = "" delta_fields = {} for field in objects.system.fields: if rpc_isystem[field] != patched_system[field]: rpc_isystem[field] = patched_system[field] delta_fields[field] = patched_system[field] if field == 'name': name = rpc_isystem[field] if field == 'contact': contact = rpc_isystem[field] if field == 'location': location = rpc_isystem[field] if field == 'system_mode': system_mode = rpc_isystem[field] if field == 'timezone': timezone = rpc_isystem[field] if field == 'capabilities': capabilities = rpc_isystem[field] if field == 'distributed_cloud_role': distributed_cloud_role = rpc_isystem[field] if field == 'security_feature': security_feature = rpc_isystem[field] delta = rpc_isystem.obj_what_changed() delta_handle = list(delta) rpc_isystem.save() pecan.request.rpcapi.evaluate_apps_reapply( pecan.request.context, trigger={'type': constants.APP_EVALUATE_REAPPLY_TYPE_SYSTEM_MODIFY, 'delta_fields': delta_fields}) if name: LOG.info("update system name") pecan.request.rpcapi.configure_isystemname(pecan.request.context, name) if name or location or contact: LOG.info("update SNMP config") pecan.request.rpcapi.update_snmp_config(pecan.request.context) if 'system_mode' in delta_handle: LOG.info("update system mode %s" % system_mode) pecan.request.rpcapi.update_system_mode_config( pecan.request.context) if timezone: LOG.info("update system timezone to %s" % timezone) pecan.request.rpcapi.configure_system_timezone( pecan.request.context) if capabilities: if change_sdn: LOG.info("update sdn to %s" % capabilities) pecan.request.rpcapi.update_sdn_enabled(pecan.request.context) if change_https: LOG.info("update https to %s" % capabilities) pecan.request.rpcapi.configure_system_https( pecan.request.context) if vswitch_type: LOG.info("update vswitch_type to %s" % capabilities) pecan.request.rpcapi.update_vswitch_type( pecan.request.context) if distributed_cloud_role and change_dc_role: LOG.info("update distributed cloud role to %s" % distributed_cloud_role) pecan.request.rpcapi.update_distributed_cloud_role( pecan.request.context) # check if we need to config the system controller database if (change_dc_role and distributed_cloud_role == constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER): hosts = pecan.request.dbapi.ihost_get_by_personality( constants.CONTROLLER) # this is a replay case after the first host has been created if len(hosts) == 1: pecan.request.rpcapi.configure_system_controller( pecan.request.context, hosts[0]) if 'security_feature' in delta_handle: LOG.info("update security_feature %s" % security_feature) pecan.request.rpcapi.update_security_feature_config( pecan.request.context) return System.convert_with_links(rpc_isystem) @cutils.synchronized(LOCK_NAME) @wsme_pecan.wsexpose(None, types.uuid, status_code=204) def delete(self, isystem_uuid): """Delete a isystem. :param isystem_uuid: UUID of a isystem. """ raise exception.OperationNotPermitted @wsme_pecan.wsexpose(int) def mgmtvlan(self): local_hostname = cutils.get_local_controller_hostname() controller = pecan.request.dbapi.ihost_get(local_hostname) host_id = controller['id'] interface_list = pecan.request.dbapi.iinterface_get_by_ihost(host_id) for interface in interface_list: if constants.NETWORK_TYPE_MGMT in interface['networktypelist']: if 'vlan_id' not in interface: return 0 else: return interface['vlan_id'] return None