diff --git a/deploy/operator/api/roundtrip_fuzz_test.go b/deploy/operator/api/roundtrip_fuzz_test.go
index 1c2dd91cf6b2..9d313bf7cac5 100644
--- a/deploy/operator/api/roundtrip_fuzz_test.go
+++ b/deploy/operator/api/roundtrip_fuzz_test.go
@@ -179,6 +179,28 @@ func dynamoFuzzerFuncs(_ runtimeserializer.CodecFactory) []any {
fuzzAlphaDGDRStatus,
fuzzBetaDGDRSpec,
fuzzBetaDGDRStatus,
+ // PlacementStatus (v1alpha1 + v1beta1): pick admissible values so the
+ // round-trip fuzzer exercises Placement without producing shapes the CRD
+ // schema rejects. State draws from the enum; Score draws either nil or a
+ // value inside the [0, 1] bounds enforced by the CRD.
+ func(p *v1beta1.PlacementStatus, c randfill.Continue) {
+ p.State = oneOf(c,
+ v1beta1.PlacementScoreStateReported,
+ v1beta1.PlacementScoreStatePartial,
+ v1beta1.PlacementScoreStateUnsupported,
+ v1beta1.PlacementScoreStateUnknown,
+ )
+ p.Score = oneOfPtr(c, 0.0, 0.25, 0.5, 0.75, 1.0)
+ },
+ func(p *v1alpha1.PlacementStatus, c randfill.Continue) {
+ p.State = oneOf(c,
+ v1alpha1.PlacementScoreStateReported,
+ v1alpha1.PlacementScoreStatePartial,
+ v1alpha1.PlacementScoreStateUnsupported,
+ v1alpha1.PlacementScoreStateUnknown,
+ )
+ p.Score = oneOfPtr(c, 0.0, 0.25, 0.5, 0.75, 1.0)
+ },
// v1beta1 Components: the listMapKey marker requires name
// to be non-empty and unique; MaxItems caps the length at 25.
// Enforce both so the input is admissible.
@@ -308,6 +330,17 @@ func oneOf[T any](c randfill.Continue, values ...T) T {
return values[c.Intn(len(values))]
}
+// oneOfPtr returns nil roughly half the time; otherwise a pointer to one of
+// values. Useful for optional API fields that must either be unset or draw
+// from a constrained value set.
+func oneOfPtr[T any](c randfill.Continue, values ...T) *T {
+ if c.Bool() {
+ return nil
+ }
+ v := oneOf(c, values...)
+ return &v
+}
+
func fuzzJSONValue(c randfill.Continue, depth int) any {
if depth >= 2 {
switch c.Uint32() % 5 {
diff --git a/deploy/operator/api/v1alpha1/conversion_field_coverage_test.go b/deploy/operator/api/v1alpha1/conversion_field_coverage_test.go
index 4f338d8c5eba..90c1879770e6 100644
--- a/deploy/operator/api/v1alpha1/conversion_field_coverage_test.go
+++ b/deploy/operator/api/v1alpha1/conversion_field_coverage_test.go
@@ -156,6 +156,8 @@ DynamoGraphDeploymentStatus.components.scheduledReplicas
DynamoGraphDeploymentStatus.components.updatedReplicas
DynamoGraphDeploymentStatus.conditions
DynamoGraphDeploymentStatus.observedGeneration
+DynamoGraphDeploymentStatus.placement.score
+DynamoGraphDeploymentStatus.placement.state
DynamoGraphDeploymentStatus.restart.inProgress
DynamoGraphDeploymentStatus.restart.observedID
DynamoGraphDeploymentStatus.restart.phase
diff --git a/deploy/operator/api/v1alpha1/dynamographdeployment_conversion.go b/deploy/operator/api/v1alpha1/dynamographdeployment_conversion.go
index a0a5c1fbb7f7..3749c5a8e8f4 100644
--- a/deploy/operator/api/v1alpha1/dynamographdeployment_conversion.go
+++ b/deploy/operator/api/v1alpha1/dynamographdeployment_conversion.go
@@ -640,6 +640,16 @@ func ConvertToSpecTopologyConstraint(src *v1beta1.SpecTopologyConstraint, dst *S
func ConvertFromDynamoGraphDeploymentStatus(src *DynamoGraphDeploymentStatus, dst *v1beta1.DynamoGraphDeploymentStatus) {
dst.ObservedGeneration = src.ObservedGeneration
dst.State = v1beta1.DGDState(src.State)
+ if src.Placement != nil {
+ dst.Placement = &v1beta1.PlacementStatus{
+ State: v1beta1.PlacementScoreState(src.Placement.State),
+ }
+ if src.Placement.Score != nil {
+ dst.Placement.Score = ptr.To(*src.Placement.Score)
+ }
+ } else {
+ dst.Placement = nil
+ }
if len(src.Conditions) > 0 {
dst.Conditions = make([]metav1.Condition, 0, len(src.Conditions))
for _, c := range src.Conditions {
@@ -677,6 +687,16 @@ func ConvertFromDynamoGraphDeploymentStatus(src *DynamoGraphDeploymentStatus, ds
func ConvertToDynamoGraphDeploymentStatus(src *v1beta1.DynamoGraphDeploymentStatus, dst *DynamoGraphDeploymentStatus) {
dst.ObservedGeneration = src.ObservedGeneration
dst.State = DGDState(src.State)
+ if src.Placement != nil {
+ dst.Placement = &PlacementStatus{
+ State: PlacementScoreState(src.Placement.State),
+ }
+ if src.Placement.Score != nil {
+ dst.Placement.Score = ptr.To(*src.Placement.Score)
+ }
+ } else {
+ dst.Placement = nil
+ }
if len(src.Conditions) > 0 {
dst.Conditions = make([]metav1.Condition, 0, len(src.Conditions))
for _, c := range src.Conditions {
diff --git a/deploy/operator/api/v1alpha1/dynamographdeployment_conversion_test.go b/deploy/operator/api/v1alpha1/dynamographdeployment_conversion_test.go
index 07d33293b322..6d1c1f684311 100644
--- a/deploy/operator/api/v1alpha1/dynamographdeployment_conversion_test.go
+++ b/deploy/operator/api/v1alpha1/dynamographdeployment_conversion_test.go
@@ -912,6 +912,10 @@ func TestDGD_RoundTrip_Status(t *testing.T) {
Status: v1beta1.DynamoGraphDeploymentStatus{
ObservedGeneration: 7,
State: v1beta1.DGDStateSuccessful,
+ Placement: &v1beta1.PlacementStatus{
+ Score: ptr.To(0.87),
+ State: v1beta1.PlacementScoreStateReported,
+ },
Conditions: []metav1.Condition{
{
Type: "Ready",
diff --git a/deploy/operator/api/v1alpha1/dynamographdeployment_types.go b/deploy/operator/api/v1alpha1/dynamographdeployment_types.go
index 300247a4d480..32c5e4e453ab 100644
--- a/deploy/operator/api/v1alpha1/dynamographdeployment_types.go
+++ b/deploy/operator/api/v1alpha1/dynamographdeployment_types.go
@@ -54,6 +54,34 @@ const (
DGDStateFailed DGDState = "failed"
)
+// PlacementScoreState describes whether placement score is available and how
+// complete the reported score is for a graph deployment. See the v1beta1
+// PlacementScoreState for the authoritative semantics of each value.
+// +kubebuilder:validation:Enum=Reported;Partial;Unsupported;Unknown
+type PlacementScoreState string
+
+const (
+ PlacementScoreStateReported PlacementScoreState = "Reported"
+ PlacementScoreStatePartial PlacementScoreState = "Partial"
+ PlacementScoreStateUnsupported PlacementScoreState = "Unsupported"
+ PlacementScoreStateUnknown PlacementScoreState = "Unknown"
+)
+
+// PlacementStatus groups DGD-level scheduler placement fields under a single
+// status object so future placement signals can be added without a schema
+// break. See the v1beta1 PlacementStatus for the authoritative field docs.
+type PlacementStatus struct {
+ // Score is the DGD-level scheduler placement score. Normalized to [0.0, 1.0]
+ // where higher is better and 1.0 is the best possible placement.
+ // +optional
+ // +kubebuilder:validation:Minimum=0
+ // +kubebuilder:validation:Maximum=1
+ Score *float64 `json:"score,omitempty"`
+ // State indicates placement score reporting state.
+ // +optional
+ State PlacementScoreState `json:"state,omitempty"`
+}
+
// DynamoGraphDeploymentSpec defines the desired state of DynamoGraphDeployment.
// +kubebuilder:validation:XValidation:rule="oldSelf.hasValue() || !has(self.restart)",message="spec.restart must be unset on create; set spec.restart.id after creation to request a restart",optionalOldSelf=true
type DynamoGraphDeploymentSpec struct {
@@ -169,6 +197,10 @@ type DynamoGraphDeploymentStatus struct {
// Currently only supported for singl-node, non-Grove deployments (DCD/Deployment).
// +optional
RollingUpdate *RollingUpdateStatus `json:"rollingUpdate,omitempty"`
+ // Placement groups DGD-level scheduler placement signals (score, reporting
+ // state, and any future placement fields).
+ // +optional
+ Placement *PlacementStatus `json:"placement,omitempty"`
}
// ServiceCheckpointStatus contains checkpoint information for a single service.
diff --git a/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go b/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go
index b0b21b20774f..ed2f2ab80449 100644
--- a/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go
+++ b/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go
@@ -1052,6 +1052,11 @@ func (in *DynamoGraphDeploymentStatus) DeepCopyInto(out *DynamoGraphDeploymentSt
*out = new(RollingUpdateStatus)
(*in).DeepCopyInto(*out)
}
+ if in.Placement != nil {
+ in, out := &in.Placement, &out.Placement
+ *out = new(PlacementStatus)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoGraphDeploymentStatus.
@@ -1529,6 +1534,26 @@ func (in *PVC) DeepCopy() *PVC {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PlacementStatus) DeepCopyInto(out *PlacementStatus) {
+ *out = *in
+ if in.Score != nil {
+ in, out := &in.Score, &out.Score
+ *out = new(float64)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementStatus.
+func (in *PlacementStatus) DeepCopy() *PlacementStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(PlacementStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodReference) DeepCopyInto(out *PodReference) {
*out = *in
diff --git a/deploy/operator/api/v1beta1/common.go b/deploy/operator/api/v1beta1/common.go
index b3ba29bd53ac..756bf18a33c6 100644
--- a/deploy/operator/api/v1beta1/common.go
+++ b/deploy/operator/api/v1beta1/common.go
@@ -564,6 +564,53 @@ const (
DGDStateFailed DGDState = "failed"
)
+// PlacementScoreState describes whether placement score is available and how
+// complete the reported score is for a graph deployment.
+//
+// Every backend must set this field after the first reconciliation:
+// - Reported: a score is available for every scored placement unit.
+// - Partial: a score is available for some but not all placement units.
+// - Unsupported: the backend does not surface a placement score at all.
+// - Unknown: the backend supports scores but the current value is
+// indeterminate (e.g. read failure, not yet populated by the
+// scheduler). When set, PlacementStatus.Score must be cleared.
+//
+// +kubebuilder:validation:Enum=Reported;Partial;Unsupported;Unknown
+type PlacementScoreState string
+
+const (
+ PlacementScoreStateReported PlacementScoreState = "Reported"
+ PlacementScoreStatePartial PlacementScoreState = "Partial"
+ PlacementScoreStateUnsupported PlacementScoreState = "Unsupported"
+ PlacementScoreStateUnknown PlacementScoreState = "Unknown"
+)
+
+// PlacementStatus groups DGD-level scheduler placement fields under a single
+// status object so future placement signals (e.g. scheduler contract version,
+// last-report timestamp, per-unit reports) can be added without a schema break.
+//
+// The score source is an open question in DEP #10064 (Grove mirror, typed Grove
+// scheduler API, or unstructured provider). Until a source is selected and
+// implemented, the DGD controller does not write this field; the schema and
+// conversion are landed here so downstream consumers can rely on the shape.
+type PlacementStatus struct {
+ // score is the DGD-level scheduler placement score aggregated from
+ // relevant scheduler placement units. Normalized to [0.0, 1.0] where higher
+ // is better and 1.0 represents the best possible placement. Aggregation
+ // uses the minimum across placement units so the value is a worst-placement
+ // signal for the graph. Scores are only comparable across DGDs that share
+ // the same scheduler scoring contract and version.
+ // +optional
+ // +kubebuilder:validation:Minimum=0
+ // +kubebuilder:validation:Maximum=1
+ Score *float64 `json:"score,omitempty"`
+
+ // state indicates placement score reporting state. See PlacementScoreState
+ // for the semantics of each value.
+ // +optional
+ State PlacementScoreState `json:"state,omitempty"`
+}
+
// RestartPhase enumerates phases of a graph-level restart.
type RestartPhase string
diff --git a/deploy/operator/api/v1beta1/dynamographdeployment_types.go b/deploy/operator/api/v1beta1/dynamographdeployment_types.go
index 89cf9585fda0..78e532e9950e 100644
--- a/deploy/operator/api/v1beta1/dynamographdeployment_types.go
+++ b/deploy/operator/api/v1beta1/dynamographdeployment_types.go
@@ -126,6 +126,11 @@ type DynamoGraphDeploymentStatus struct {
// Currently only supported for single-node, non-Grove deployments (DCD/Deployment).
// +optional
RollingUpdate *RollingUpdateStatus `json:"rollingUpdate,omitempty"`
+
+ // placement groups DGD-level scheduler placement signals (score, reporting
+ // state, and any future placement fields).
+ // +optional
+ Placement *PlacementStatus `json:"placement,omitempty"`
}
// DGD Ready condition reasons used to classify Grove-backed not-ready
diff --git a/deploy/operator/api/v1beta1/zz_generated.deepcopy.go b/deploy/operator/api/v1beta1/zz_generated.deepcopy.go
index b9beb2e0c91c..dd8efdd35fac 100644
--- a/deploy/operator/api/v1beta1/zz_generated.deepcopy.go
+++ b/deploy/operator/api/v1beta1/zz_generated.deepcopy.go
@@ -810,6 +810,11 @@ func (in *DynamoGraphDeploymentStatus) DeepCopyInto(out *DynamoGraphDeploymentSt
*out = new(RollingUpdateStatus)
(*in).DeepCopyInto(*out)
}
+ if in.Placement != nil {
+ in, out := &in.Placement, &out.Placement
+ *out = new(PlacementStatus)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoGraphDeploymentStatus.
@@ -1131,6 +1136,26 @@ func (in *ParetoConfig) DeepCopy() *ParetoConfig {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PlacementStatus) DeepCopyInto(out *PlacementStatus) {
+ *out = *in
+ if in.Score != nil {
+ in, out := &in.Score, &out.Score
+ *out = new(float64)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementStatus.
+func (in *PlacementStatus) DeepCopy() *PlacementStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(PlacementStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProfilingResultsStatus) DeepCopyInto(out *ProfilingResultsStatus) {
*out = *in
diff --git a/deploy/operator/config/crd/bases/nvidia.com_dynamographdeployments.yaml b/deploy/operator/config/crd/bases/nvidia.com_dynamographdeployments.yaml
index fd90a86359c0..f39545afcf05 100644
--- a/deploy/operator/config/crd/bases/nvidia.com_dynamographdeployments.yaml
+++ b/deploy/operator/config/crd/bases/nvidia.com_dynamographdeployments.yaml
@@ -11951,6 +11951,27 @@ spec:
description: ObservedGeneration is the most recent generation observed by the controller.
format: int64
type: integer
+ placement:
+ description: |-
+ Placement groups DGD-level scheduler placement signals (score, reporting
+ state, and any future placement fields).
+ properties:
+ score:
+ description: |-
+ Score is the DGD-level scheduler placement score. Normalized to [0.0, 1.0]
+ where higher is better and 1.0 is the best possible placement.
+ maximum: 1
+ minimum: 0
+ type: number
+ state:
+ description: State indicates placement score reporting state.
+ enum:
+ - Reported
+ - Partial
+ - Unsupported
+ - Unknown
+ type: string
+ type: object
restart:
description: Restart contains the status of the restart of the graph deployment.
properties:
@@ -21370,6 +21391,33 @@ spec:
description: observedGeneration is the most recent generation observed by the controller.
format: int64
type: integer
+ placement:
+ description: |-
+ placement groups DGD-level scheduler placement signals (score, reporting
+ state, and any future placement fields).
+ properties:
+ score:
+ description: |-
+ score is the DGD-level scheduler placement score aggregated from
+ relevant scheduler placement units. Normalized to [0.0, 1.0] where higher
+ is better and 1.0 represents the best possible placement. Aggregation
+ uses the minimum across placement units so the value is a worst-placement
+ signal for the graph. Scores are only comparable across DGDs that share
+ the same scheduler scoring contract and version.
+ maximum: 1
+ minimum: 0
+ type: number
+ state:
+ description: |-
+ state indicates placement score reporting state. See PlacementScoreState
+ for the semantics of each value.
+ enum:
+ - Reported
+ - Partial
+ - Unsupported
+ - Unknown
+ type: string
+ type: object
restart:
description: restart contains the status of a graph-level restart.
properties:
diff --git a/docs/kubernetes/api-reference.md b/docs/kubernetes/api-reference.md
index c5fd2036e848..9bad1f3ec8fe 100644
--- a/docs/kubernetes/api-reference.md
+++ b/docs/kubernetes/api-reference.md
@@ -738,6 +738,7 @@ _Appears in:_
| `restart` _[RestartStatus](#restartstatus)_ | Restart contains the status of the restart of the graph deployment. | | Optional: \{\}
|
| `checkpoints` _object (keys:string, values:[ServiceCheckpointStatus](#servicecheckpointstatus))_ | Checkpoints contains per-service checkpoint status information.
The map key is the service name from spec.services. | | Optional: \{\}
|
| `rollingUpdate` _[RollingUpdateStatus](#rollingupdatestatus)_ | RollingUpdate tracks the progress of operator manged rolling updates.
Currently only supported for singl-node, non-Grove deployments (DCD/Deployment). | | Optional: \{\}
|
+| `placement` _[PlacementStatus](#placementstatus)_ | Placement groups DGD-level scheduler placement signals (score, reporting
state, and any future placement fields). | | Optional: \{\}
|
#### DynamoModel
@@ -1127,6 +1128,47 @@ _Appears in:_
| `volumeAccessMode` _[PersistentVolumeAccessMode](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#persistentvolumeaccessmode-v1-core)_ | VolumeAccessMode is the volume access mode of the PVC. Required when create is true. | | |
+#### PlacementScoreState
+
+_Underlying type:_ _string_
+
+PlacementScoreState describes whether placement score is available and how
+complete the reported score is for a graph deployment. See the v1beta1
+PlacementScoreState for the authoritative semantics of each value.
+
+_Validation:_
+- Enum: [Reported Partial Unsupported Unknown]
+
+_Appears in:_
+- [PlacementStatus](#placementstatus)
+
+| Field | Description |
+| --- | --- |
+| `Reported` | |
+| `Partial` | |
+| `Unsupported` | |
+| `Unknown` | |
+
+
+#### PlacementStatus
+
+
+
+PlacementStatus groups DGD-level scheduler placement fields under a single
+status object so future placement signals can be added without a schema
+break. See the v1beta1 PlacementStatus for the authoritative field docs.
+
+
+
+_Appears in:_
+- [DynamoGraphDeploymentStatus](#dynamographdeploymentstatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `score` _float_ | Score is the DGD-level scheduler placement score. Normalized to [0.0, 1.0]
where higher is better and 1.0 is the best possible placement. | | Maximum: 1
Minimum: 0
Optional: \{\}
|
+| `state` _[PlacementScoreState](#placementscorestate)_ | State indicates placement score reporting state. | | Enum: [Reported Partial Unsupported Unknown]
Optional: \{\}
|
+
+
#### PodReference
@@ -2376,6 +2418,7 @@ _Appears in:_
| `restart` _[RestartStatus](#restartstatus)_ | restart contains the status of a graph-level restart. | | Optional: \{\}
|
| `checkpoints` _object (keys:string, values:[ComponentCheckpointStatus](#componentcheckpointstatus))_ | checkpoints contains per-component checkpoint status, keyed by component name. | | Optional: \{\}
|
| `rollingUpdate` _[RollingUpdateStatus](#rollingupdatestatus)_ | rollingUpdate tracks the progress of operator-managed rolling updates.
Currently only supported for single-node, non-Grove deployments (DCD/Deployment). | | Optional: \{\}
|
+| `placement` _[PlacementStatus](#placementstatus)_ | placement groups DGD-level scheduler placement signals (score, reporting
state, and any future placement fields). | | Optional: \{\}
|
#### EPPConfig
@@ -2743,6 +2786,59 @@ _Appears in:_
| `config` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#rawextension-runtime-pkg)_ | Config is the full deployment configuration for this Pareto point. | | Type: object
|
+#### PlacementScoreState
+
+_Underlying type:_ _string_
+
+PlacementScoreState describes whether placement score is available and how
+complete the reported score is for a graph deployment.
+
+Every backend must set this field after the first reconciliation:
+ - Reported: a score is available for every scored placement unit.
+ - Partial: a score is available for some but not all placement units.
+ - Unsupported: the backend does not surface a placement score at all.
+ - Unknown: the backend supports scores but the current value is
+ indeterminate (e.g. read failure, not yet populated by the
+ scheduler). When set, PlacementStatus.Score must be cleared.
+
+_Validation:_
+- Enum: [Reported Partial Unsupported Unknown]
+
+_Appears in:_
+- [PlacementStatus](#placementstatus)
+
+| Field | Description |
+| --- | --- |
+| `Reported` | |
+| `Partial` | |
+| `Unsupported` | |
+| `Unknown` | |
+
+
+#### PlacementStatus
+
+
+
+PlacementStatus groups DGD-level scheduler placement fields under a single
+status object so future placement signals (e.g. scheduler contract version,
+last-report timestamp, per-unit reports) can be added without a schema break.
+
+The score source is an open question in DEP #10064 (Grove mirror, typed Grove
+scheduler API, or unstructured provider). Until a source is selected and
+implemented, the DGD controller does not write this field; the schema and
+conversion are landed here so downstream consumers can rely on the shape.
+
+
+
+_Appears in:_
+- [DynamoGraphDeploymentStatus](#dynamographdeploymentstatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `score` _float_ | score is the DGD-level scheduler placement score aggregated from
relevant scheduler placement units. Normalized to [0.0, 1.0] where higher
is better and 1.0 represents the best possible placement. Aggregation
uses the minimum across placement units so the value is a worst-placement
signal for the graph. Scores are only comparable across DGDs that share
the same scheduler scoring contract and version. | | Maximum: 1
Minimum: 0
Optional: \{\}
|
+| `state` _[PlacementScoreState](#placementscorestate)_ | state indicates placement score reporting state. See PlacementScoreState
for the semantics of each value. | | Enum: [Reported Partial Unsupported Unknown]
Optional: \{\}
|
+
+
#### ProfilingPhase
_Underlying type:_ _string_