Exemple #1
0
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()
Exemple #2
0
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
Exemple #3
0
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