[VOL-2778] Introducing Service definition in order to support the TT workflow

Change-Id: Ib171502e8940b5d0b219620a4503f7095d376d7a
diff --git a/Makefile b/Makefile
index bb8c5e9..21d73e0 100644
--- a/Makefile
+++ b/Makefile
@@ -96,7 +96,7 @@
 	exit $$RETURN
 
 test-bbr: release-bbr docker-build # @HELP Validate that BBSim and BBR are working together
-	DOCKER_RUN_ARGS="-auth -dhcp -pon 2 -onu 2" make docker-run
+	DOCKER_RUN_ARGS="-pon 2 -onu 2" make docker-run
 	sleep 5
 	./$(RELEASE_DIR)/$(RELEASE_BBR_NAME)-linux-amd64 -pon 2 -onu 2
 	docker rm -f bbsim
diff --git a/VERSION b/VERSION
index 0d91a54..05639a5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.0
+1.0.0-dev
diff --git a/api/bbsim/bbsim.pb.go b/api/bbsim/bbsim.pb.go
index 5d4a80d..39f8616 100644
--- a/api/bbsim/bbsim.pb.go
+++ b/api/bbsim/bbsim.pb.go
@@ -131,7 +131,7 @@
 }
 
 func (AlarmType_Types) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{8, 0}
+	return fileDescriptor_ef7750073d18011b, []int{10, 0}
 }
 
 type PONPort struct {
@@ -316,18 +316,17 @@
 }
 
 type ONU struct {
-	ID                   int32    `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
-	SerialNumber         string   `protobuf:"bytes,2,opt,name=SerialNumber,proto3" json:"SerialNumber,omitempty"`
-	OperState            string   `protobuf:"bytes,3,opt,name=OperState,proto3" json:"OperState,omitempty"`
-	InternalState        string   `protobuf:"bytes,4,opt,name=InternalState,proto3" json:"InternalState,omitempty"`
-	PonPortID            int32    `protobuf:"varint,5,opt,name=PonPortID,proto3" json:"PonPortID,omitempty"`
-	STag                 int32    `protobuf:"varint,6,opt,name=STag,proto3" json:"STag,omitempty"`
-	CTag                 int32    `protobuf:"varint,7,opt,name=CTag,proto3" json:"CTag,omitempty"`
-	HwAddress            string   `protobuf:"bytes,8,opt,name=HwAddress,proto3" json:"HwAddress,omitempty"`
-	PortNo               int32    `protobuf:"varint,9,opt,name=PortNo,proto3" json:"PortNo,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	ID                   int32      `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
+	SerialNumber         string     `protobuf:"bytes,2,opt,name=SerialNumber,proto3" json:"SerialNumber,omitempty"`
+	OperState            string     `protobuf:"bytes,3,opt,name=OperState,proto3" json:"OperState,omitempty"`
+	InternalState        string     `protobuf:"bytes,4,opt,name=InternalState,proto3" json:"InternalState,omitempty"`
+	PonPortID            int32      `protobuf:"varint,5,opt,name=PonPortID,proto3" json:"PonPortID,omitempty"`
+	HwAddress            string     `protobuf:"bytes,8,opt,name=HwAddress,proto3" json:"HwAddress,omitempty"`
+	PortNo               int32      `protobuf:"varint,9,opt,name=PortNo,proto3" json:"PortNo,omitempty"`
+	Services             []*Service `protobuf:"bytes,10,rep,name=services,proto3" json:"services,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
 }
 
 func (m *ONU) Reset()         { *m = ONU{} }
@@ -390,20 +389,6 @@
 	return 0
 }
 
-func (m *ONU) GetSTag() int32 {
-	if m != nil {
-		return m.STag
-	}
-	return 0
-}
-
-func (m *ONU) GetCTag() int32 {
-	if m != nil {
-		return m.CTag
-	}
-	return 0
-}
-
 func (m *ONU) GetHwAddress() string {
 	if m != nil {
 		return m.HwAddress
@@ -418,6 +403,132 @@
 	return 0
 }
 
+func (m *ONU) GetServices() []*Service {
+	if m != nil {
+		return m.Services
+	}
+	return nil
+}
+
+type Service struct {
+	Name                 string   `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
+	HwAddress            string   `protobuf:"bytes,2,opt,name=HwAddress,proto3" json:"HwAddress,omitempty"`
+	OnuSn                string   `protobuf:"bytes,3,opt,name=OnuSn,proto3" json:"OnuSn,omitempty"`
+	STag                 int32    `protobuf:"varint,4,opt,name=STag,proto3" json:"STag,omitempty"`
+	CTag                 int32    `protobuf:"varint,5,opt,name=CTag,proto3" json:"CTag,omitempty"`
+	NeedsEapol           bool     `protobuf:"varint,6,opt,name=NeedsEapol,proto3" json:"NeedsEapol,omitempty"`
+	NeedsDhcp            bool     `protobuf:"varint,7,opt,name=NeedsDhcp,proto3" json:"NeedsDhcp,omitempty"`
+	NeedsIgmp            bool     `protobuf:"varint,8,opt,name=NeedsIgmp,proto3" json:"NeedsIgmp,omitempty"`
+	GemPort              int32    `protobuf:"varint,9,opt,name=GemPort,proto3" json:"GemPort,omitempty"`
+	EapolState           string   `protobuf:"bytes,10,opt,name=EapolState,proto3" json:"EapolState,omitempty"`
+	DhcpState            string   `protobuf:"bytes,11,opt,name=DhcpState,proto3" json:"DhcpState,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Service) Reset()         { *m = Service{} }
+func (m *Service) String() string { return proto.CompactTextString(m) }
+func (*Service) ProtoMessage()    {}
+func (*Service) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ef7750073d18011b, []int{4}
+}
+
+func (m *Service) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Service.Unmarshal(m, b)
+}
+func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Service.Marshal(b, m, deterministic)
+}
+func (m *Service) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Service.Merge(m, src)
+}
+func (m *Service) XXX_Size() int {
+	return xxx_messageInfo_Service.Size(m)
+}
+func (m *Service) XXX_DiscardUnknown() {
+	xxx_messageInfo_Service.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Service proto.InternalMessageInfo
+
+func (m *Service) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Service) GetHwAddress() string {
+	if m != nil {
+		return m.HwAddress
+	}
+	return ""
+}
+
+func (m *Service) GetOnuSn() string {
+	if m != nil {
+		return m.OnuSn
+	}
+	return ""
+}
+
+func (m *Service) GetSTag() int32 {
+	if m != nil {
+		return m.STag
+	}
+	return 0
+}
+
+func (m *Service) GetCTag() int32 {
+	if m != nil {
+		return m.CTag
+	}
+	return 0
+}
+
+func (m *Service) GetNeedsEapol() bool {
+	if m != nil {
+		return m.NeedsEapol
+	}
+	return false
+}
+
+func (m *Service) GetNeedsDhcp() bool {
+	if m != nil {
+		return m.NeedsDhcp
+	}
+	return false
+}
+
+func (m *Service) GetNeedsIgmp() bool {
+	if m != nil {
+		return m.NeedsIgmp
+	}
+	return false
+}
+
+func (m *Service) GetGemPort() int32 {
+	if m != nil {
+		return m.GemPort
+	}
+	return 0
+}
+
+func (m *Service) GetEapolState() string {
+	if m != nil {
+		return m.EapolState
+	}
+	return ""
+}
+
+func (m *Service) GetDhcpState() string {
+	if m != nil {
+		return m.DhcpState
+	}
+	return ""
+}
+
 type ONUTrafficSchedulers struct {
 	TraffSchedulers      *tech_profile.TrafficSchedulers `protobuf:"bytes,1,opt,name=traffSchedulers,proto3" json:"traffSchedulers,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}                        `json:"-"`
@@ -429,7 +540,7 @@
 func (m *ONUTrafficSchedulers) String() string { return proto.CompactTextString(m) }
 func (*ONUTrafficSchedulers) ProtoMessage()    {}
 func (*ONUTrafficSchedulers) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{4}
+	return fileDescriptor_ef7750073d18011b, []int{5}
 }
 
 func (m *ONUTrafficSchedulers) XXX_Unmarshal(b []byte) error {
@@ -468,7 +579,7 @@
 func (m *ONUs) String() string { return proto.CompactTextString(m) }
 func (*ONUs) ProtoMessage()    {}
 func (*ONUs) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{5}
+	return fileDescriptor_ef7750073d18011b, []int{6}
 }
 
 func (m *ONUs) XXX_Unmarshal(b []byte) error {
@@ -496,6 +607,45 @@
 	return nil
 }
 
+type Services struct {
+	Items                []*Service `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *Services) Reset()         { *m = Services{} }
+func (m *Services) String() string { return proto.CompactTextString(m) }
+func (*Services) ProtoMessage()    {}
+func (*Services) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ef7750073d18011b, []int{7}
+}
+
+func (m *Services) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Services.Unmarshal(m, b)
+}
+func (m *Services) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Services.Marshal(b, m, deterministic)
+}
+func (m *Services) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Services.Merge(m, src)
+}
+func (m *Services) XXX_Size() int {
+	return xxx_messageInfo_Services.Size(m)
+}
+func (m *Services) XXX_DiscardUnknown() {
+	xxx_messageInfo_Services.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Services proto.InternalMessageInfo
+
+func (m *Services) GetItems() []*Service {
+	if m != nil {
+		return m.Items
+	}
+	return nil
+}
+
 type ONURequest struct {
 	SerialNumber         string   `protobuf:"bytes,1,opt,name=SerialNumber,proto3" json:"SerialNumber,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -507,7 +657,7 @@
 func (m *ONURequest) String() string { return proto.CompactTextString(m) }
 func (*ONURequest) ProtoMessage()    {}
 func (*ONURequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{6}
+	return fileDescriptor_ef7750073d18011b, []int{8}
 }
 
 func (m *ONURequest) XXX_Unmarshal(b []byte) error {
@@ -546,7 +696,7 @@
 func (m *PONRequest) String() string { return proto.CompactTextString(m) }
 func (*PONRequest) ProtoMessage()    {}
 func (*PONRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{7}
+	return fileDescriptor_ef7750073d18011b, []int{9}
 }
 
 func (m *PONRequest) XXX_Unmarshal(b []byte) error {
@@ -584,7 +734,7 @@
 func (m *AlarmType) String() string { return proto.CompactTextString(m) }
 func (*AlarmType) ProtoMessage()    {}
 func (*AlarmType) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{8}
+	return fileDescriptor_ef7750073d18011b, []int{10}
 }
 
 func (m *AlarmType) XXX_Unmarshal(b []byte) error {
@@ -617,7 +767,7 @@
 func (m *AlarmParameter) String() string { return proto.CompactTextString(m) }
 func (*AlarmParameter) ProtoMessage()    {}
 func (*AlarmParameter) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{9}
+	return fileDescriptor_ef7750073d18011b, []int{11}
 }
 
 func (m *AlarmParameter) XXX_Unmarshal(b []byte) error {
@@ -669,7 +819,7 @@
 func (m *ONUAlarmRequest) String() string { return proto.CompactTextString(m) }
 func (*ONUAlarmRequest) ProtoMessage()    {}
 func (*ONUAlarmRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{10}
+	return fileDescriptor_ef7750073d18011b, []int{12}
 }
 
 func (m *ONUAlarmRequest) XXX_Unmarshal(b []byte) error {
@@ -732,7 +882,7 @@
 func (m *OLTAlarmRequest) String() string { return proto.CompactTextString(m) }
 func (*OLTAlarmRequest) ProtoMessage()    {}
 func (*OLTAlarmRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{11}
+	return fileDescriptor_ef7750073d18011b, []int{13}
 }
 
 func (m *OLTAlarmRequest) XXX_Unmarshal(b []byte) error {
@@ -788,7 +938,7 @@
 func (m *VersionNumber) String() string { return proto.CompactTextString(m) }
 func (*VersionNumber) ProtoMessage()    {}
 func (*VersionNumber) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{12}
+	return fileDescriptor_ef7750073d18011b, []int{14}
 }
 
 func (m *VersionNumber) XXX_Unmarshal(b []byte) error {
@@ -849,7 +999,7 @@
 func (m *LogLevel) String() string { return proto.CompactTextString(m) }
 func (*LogLevel) ProtoMessage()    {}
 func (*LogLevel) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{13}
+	return fileDescriptor_ef7750073d18011b, []int{15}
 }
 
 func (m *LogLevel) XXX_Unmarshal(b []byte) error {
@@ -896,7 +1046,7 @@
 func (m *Response) String() string { return proto.CompactTextString(m) }
 func (*Response) ProtoMessage()    {}
 func (*Response) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{14}
+	return fileDescriptor_ef7750073d18011b, []int{16}
 }
 
 func (m *Response) XXX_Unmarshal(b []byte) error {
@@ -943,7 +1093,7 @@
 func (m *IgmpRequest) String() string { return proto.CompactTextString(m) }
 func (*IgmpRequest) ProtoMessage()    {}
 func (*IgmpRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{15}
+	return fileDescriptor_ef7750073d18011b, []int{17}
 }
 
 func (m *IgmpRequest) XXX_Unmarshal(b []byte) error {
@@ -990,7 +1140,7 @@
 func (m *Flows) String() string { return proto.CompactTextString(m) }
 func (*Flows) ProtoMessage()    {}
 func (*Flows) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{16}
+	return fileDescriptor_ef7750073d18011b, []int{18}
 }
 
 func (m *Flows) XXX_Unmarshal(b []byte) error {
@@ -1035,7 +1185,7 @@
 func (m *Empty) String() string { return proto.CompactTextString(m) }
 func (*Empty) ProtoMessage()    {}
 func (*Empty) Descriptor() ([]byte, []int) {
-	return fileDescriptor_ef7750073d18011b, []int{17}
+	return fileDescriptor_ef7750073d18011b, []int{19}
 }
 
 func (m *Empty) XXX_Unmarshal(b []byte) error {
@@ -1063,8 +1213,10 @@
 	proto.RegisterType((*NNIPort)(nil), "bbsim.NNIPort")
 	proto.RegisterType((*Olt)(nil), "bbsim.Olt")
 	proto.RegisterType((*ONU)(nil), "bbsim.ONU")
+	proto.RegisterType((*Service)(nil), "bbsim.Service")
 	proto.RegisterType((*ONUTrafficSchedulers)(nil), "bbsim.ONUTrafficSchedulers")
 	proto.RegisterType((*ONUs)(nil), "bbsim.ONUs")
+	proto.RegisterType((*Services)(nil), "bbsim.Services")
 	proto.RegisterType((*ONURequest)(nil), "bbsim.ONURequest")
 	proto.RegisterType((*PONRequest)(nil), "bbsim.PONRequest")
 	proto.RegisterType((*AlarmType)(nil), "bbsim.AlarmType")
@@ -1082,92 +1234,101 @@
 func init() { proto.RegisterFile("api/bbsim/bbsim.proto", fileDescriptor_ef7750073d18011b) }
 
 var fileDescriptor_ef7750073d18011b = []byte{
-	// 1353 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xdd, 0x72, 0x9b, 0xd6,
-	0x16, 0xd6, 0x8f, 0xf5, 0xb7, 0x64, 0xc9, 0x78, 0x27, 0x76, 0x18, 0xc7, 0xe7, 0xc4, 0xc3, 0xc9,
-	0x39, 0xe3, 0x64, 0x4e, 0x9c, 0xc6, 0x6e, 0xa7, 0xc9, 0x25, 0x96, 0xb0, 0x42, 0x2d, 0x03, 0xb3,
-	0x41, 0xce, 0xe4, 0x8a, 0xc1, 0xd2, 0xb6, 0xc5, 0x0c, 0x02, 0x05, 0x50, 0x3c, 0x79, 0x80, 0xbe,
-	0x48, 0x9f, 0xa8, 0x4f, 0xd0, 0x5e, 0xf5, 0x21, 0x7a, 0xd7, 0xd9, 0x9b, 0x0d, 0x02, 0x4b, 0xe9,
-	0xa8, 0xbd, 0xe9, 0x8d, 0x66, 0xaf, 0x6f, 0x7d, 0xeb, 0x7f, 0xb3, 0x40, 0xb0, 0xe7, 0xcc, 0xdd,
-	0xd7, 0x37, 0x37, 0x91, 0x3b, 0x4b, 0x7e, 0x4f, 0xe6, 0x61, 0x10, 0x07, 0xa8, 0xc6, 0x84, 0x83,
-	0xa7, 0x9f, 0x03, 0x2f, 0x9e, 0x3a, 0x36, 0x03, 0xa3, 0xd7, 0xc1, 0x9c, 0xf8, 0x81, 0x17, 0x27,
-	0x9c, 0x83, 0xa3, 0xa2, 0x32, 0x26, 0xe3, 0x29, 0x3d, 0xdf, 0xba, 0x1e, 0x49, 0x18, 0xd2, 0xf7,
-	0xd0, 0x30, 0x74, 0xcd, 0x08, 0xc2, 0x18, 0x75, 0xa1, 0xa2, 0xf6, 0xc5, 0xf2, 0x51, 0xf9, 0xb8,
-	0x86, 0x2b, 0x6a, 0x1f, 0x1d, 0x42, 0x4b, 0x9f, 0x93, 0xd0, 0x8c, 0x9d, 0x98, 0x88, 0x95, 0xa3,
-	0xf2, 0x71, 0x0b, 0x2f, 0x01, 0x6a, 0xa8, 0x69, 0xea, 0xdf, 0x30, 0xfc, 0xad, 0x0c, 0x55, 0xdd,
-	0x5b, 0xb5, 0x92, 0x60, 0xdb, 0x24, 0xa1, 0xeb, 0x78, 0xda, 0x62, 0x76, 0x43, 0x42, 0x6e, 0x58,
-	0xc0, 0x8a, 0x9e, 0xab, 0x0f, 0x3c, 0xa3, 0xe7, 0xd0, 0x51, 0xfd, 0x98, 0x84, 0xbe, 0xe3, 0x25,
-	0x8c, 0x2d, 0xc6, 0x28, 0x82, 0x2c, 0xae, 0x21, 0x36, 0x98, 0xaa, 0xa2, 0x1a, 0xe8, 0x25, 0x34,
-	0x79, 0x21, 0x91, 0x58, 0x3b, 0xaa, 0x1e, 0xb7, 0x4f, 0xbb, 0x27, 0x49, 0x9f, 0x39, 0x8c, 0x33,
-	0x3d, 0xe5, 0xf2, 0x6e, 0x45, 0x62, 0xbd, 0xc0, 0xe5, 0x30, 0xce, 0xf4, 0xd2, 0xef, 0xb4, 0x4e,
-	0x6d, 0xf4, 0x8f, 0xd5, 0x79, 0x08, 0x2d, 0x23, 0xf0, 0x69, 0x2e, 0x6a, 0x5f, 0xac, 0xb1, 0xf0,
-	0x4b, 0x00, 0x21, 0xd8, 0x32, 0x2d, 0xe7, 0x4e, 0xac, 0x33, 0x05, 0x3b, 0x53, 0xac, 0x47, 0xb1,
-	0x46, 0x82, 0xd1, 0x33, 0xf5, 0xf2, 0xfe, 0x5e, 0x9e, 0x4c, 0x42, 0x12, 0x45, 0x62, 0x33, 0xc9,
-	0x24, 0x03, 0xd0, 0x3e, 0xd4, 0xa9, 0x3f, 0x2d, 0x10, 0x5b, 0xcc, 0x86, 0x4b, 0x92, 0x03, 0x8f,
-	0x75, 0x6d, 0x64, 0x85, 0xce, 0xed, 0xad, 0x3b, 0x36, 0xc7, 0x53, 0x32, 0x59, 0x78, 0x24, 0x8c,
-	0x90, 0x0a, 0x3b, 0x31, 0x05, 0x97, 0x10, 0x6b, 0x4c, 0xfb, 0xf4, 0xd9, 0x49, 0xe1, 0x6e, 0xae,
-	0x58, 0xe2, 0x87, 0x76, 0xd2, 0x31, 0x6c, 0xe9, 0xda, 0x28, 0x42, 0x47, 0x50, 0x73, 0x63, 0x32,
-	0xa3, 0x8e, 0xe8, 0x3c, 0x80, 0xcf, 0x43, 0xd7, 0x46, 0x38, 0x51, 0x48, 0xdf, 0x00, 0x50, 0x89,
-	0x7c, 0x5a, 0x90, 0x28, 0x5e, 0x69, 0x7f, 0x79, 0xb5, 0xfd, 0xd2, 0x4b, 0x00, 0x43, 0xd7, 0x52,
-	0x8b, 0x5c, 0x23, 0x27, 0x8c, 0xde, 0x59, 0x36, 0x72, 0x22, 0xfd, 0x52, 0x85, 0x96, 0xec, 0x39,
-	0xe1, 0xcc, 0xfa, 0x32, 0x27, 0xd2, 0xcf, 0x55, 0xa8, 0xd1, 0x43, 0x84, 0x1a, 0x50, 0x1d, 0xea,
-	0xa6, 0x50, 0x42, 0x5d, 0x80, 0xfe, 0x47, 0x55, 0x1b, 0xd8, 0x03, 0xd9, 0x34, 0x84, 0x32, 0xea,
-	0x40, 0x4b, 0xd7, 0x46, 0xb6, 0x3c, 0x94, 0xf1, 0x95, 0x50, 0x41, 0x4f, 0xe0, 0x11, 0x15, 0x4d,
-	0x4b, 0xc6, 0xd6, 0xc8, 0xb0, 0x2f, 0x64, 0x75, 0x38, 0xc2, 0x8a, 0x50, 0x45, 0xfb, 0x80, 0x98,
-	0x42, 0x1d, 0x68, 0xf2, 0xd0, 0xee, 0x2b, 0x03, 0x2c, 0xf7, 0x15, 0x61, 0x2b, 0x35, 0xe8, 0x63,
-	0xf5, 0xc2, 0xb2, 0xf5, 0x0b, 0xfb, 0x83, 0xaa, 0xf5, 0xf5, 0x0f, 0x42, 0x0d, 0x1d, 0x82, 0x48,
-	0x15, 0x43, 0xdd, 0x34, 0x29, 0xae, 0x5f, 0xf5, 0x54, 0xbb, 0xf7, 0x5e, 0xd6, 0x34, 0x65, 0x28,
-	0xd4, 0xb3, 0x38, 0xcc, 0x9d, 0x99, 0xc5, 0x69, 0xa0, 0x17, 0xf0, 0x5f, 0xaa, 0xb0, 0xb0, 0xac,
-	0x99, 0x57, 0xaa, 0x69, 0xaa, 0xba, 0x66, 0xab, 0x9a, 0xa5, 0xe0, 0x0b, 0x05, 0x2b, 0x5a, 0x4f,
-	0xb1, 0x3f, 0xc8, 0x58, 0x53, 0xb5, 0x81, 0xd0, 0x44, 0x07, 0xb0, 0xcf, 0x52, 0xef, 0x59, 0xea,
-	0xb5, 0x6c, 0x51, 0x62, 0xea, 0xa6, 0x85, 0x44, 0x36, 0x72, 0xdb, 0xc0, 0x7a, 0x4f, 0x31, 0x4d,
-	0x5a, 0xaf, 0x82, 0xb1, 0x8e, 0x05, 0x40, 0x47, 0x70, 0x98, 0xcf, 0xeb, 0x52, 0xf9, 0x68, 0x9b,
-	0x1f, 0xb5, 0x5e, 0x66, 0xdb, 0x46, 0x7b, 0xb0, 0x4b, 0x19, 0xaa, 0x35, 0xb2, 0x0d, 0x5d, 0xa3,
-	0xbd, 0xb0, 0x4c, 0x61, 0x1b, 0xed, 0x42, 0x27, 0xeb, 0x14, 0x35, 0x17, 0x3a, 0x0f, 0xa1, 0x73,
-	0xa1, 0x9b, 0x16, 0x96, 0x42, 0x46, 0xcf, 0xa6, 0x55, 0x08, 0x3b, 0x69, 0x3f, 0x0a, 0x8a, 0x1e,
-	0xcf, 0x4a, 0x40, 0x08, 0xba, 0x79, 0xed, 0x85, 0x2a, 0xec, 0xa2, 0x47, 0xb0, 0x93, 0xc7, 0xe4,
-	0x2b, 0x55, 0x40, 0xd2, 0x5b, 0xe8, 0xb2, 0xf9, 0x1a, 0x4e, 0xe8, 0xcc, 0x48, 0x4c, 0x42, 0x24,
-	0x40, 0xf5, 0x92, 0x7c, 0xe1, 0x37, 0x87, 0x1e, 0xd1, 0x63, 0xa8, 0x5d, 0x3b, 0xde, 0x22, 0xdd,
-	0x76, 0x89, 0x20, 0xfd, 0x54, 0x66, 0xfe, 0x98, 0x75, 0xee, 0x32, 0x65, 0xb7, 0x85, 0x7b, 0x58,
-	0x02, 0x1b, 0xed, 0x86, 0x7d, 0xa8, 0xd3, 0x07, 0x7c, 0x11, 0xf1, 0xc5, 0xc0, 0x25, 0xf4, 0x1d,
-	0x40, 0x96, 0x62, 0x24, 0x6e, 0xb1, 0xa7, 0x61, 0x8f, 0x3f, 0x0d, 0xc5, 0x02, 0x70, 0x8e, 0x28,
-	0x7d, 0x82, 0x1d, 0x7d, 0x68, 0x15, 0x72, 0x3c, 0x82, 0x36, 0x5b, 0x25, 0xb7, 0xce, 0x98, 0xf0,
-	0xd5, 0xd5, 0xc1, 0x79, 0x28, 0xdb, 0x40, 0x54, 0x64, 0x95, 0x54, 0x72, 0x1b, 0x28, 0x05, 0xbf,
-	0x96, 0xa9, 0xf4, 0x63, 0x19, 0x3a, 0xd7, 0x24, 0x8c, 0xdc, 0xc0, 0xe7, 0x35, 0x89, 0xd0, 0xf8,
-	0x9c, 0x00, 0xbc, 0x27, 0xa9, 0x48, 0xfb, 0x75, 0xb3, 0x70, 0xbd, 0x89, 0xe5, 0xce, 0xb2, 0x77,
-	0x49, 0x06, 0xa0, 0x7f, 0x03, 0x8c, 0x83, 0xd9, 0xcc, 0x8d, 0xdf, 0x3b, 0xd1, 0x94, 0x47, 0xc9,
-	0x21, 0xd4, 0xfa, 0xce, 0x8d, 0x79, 0x12, 0xc9, 0x96, 0x5c, 0x02, 0xd2, 0x5b, 0x68, 0x0e, 0x83,
-	0xbb, 0x21, 0xf9, 0x4c, 0x3c, 0x3a, 0x41, 0x8f, 0x1e, 0x78, 0xfc, 0x44, 0xa0, 0x15, 0x8c, 0x1d,
-	0xcf, 0xe3, 0x93, 0x68, 0x62, 0x2e, 0x49, 0x0a, 0x34, 0x31, 0x89, 0xe6, 0x81, 0x1f, 0x11, 0xf4,
-	0x0c, 0xda, 0x11, 0xf3, 0x67, 0x8f, 0x83, 0x09, 0xe1, 0x8b, 0x1e, 0x12, 0xa8, 0x17, 0x4c, 0x08,
-	0x2d, 0x6e, 0x46, 0xa2, 0xc8, 0xb9, 0x4b, 0x0b, 0x48, 0x45, 0x29, 0x82, 0xb6, 0x7a, 0x37, 0x9b,
-	0xa7, 0x7d, 0x7f, 0x01, 0x75, 0xdd, 0x5f, 0x60, 0xf2, 0x89, 0x2f, 0xc5, 0xdd, 0xdc, 0x2e, 0x4b,
-	0x28, 0x98, 0x13, 0xd0, 0x3b, 0xd8, 0x36, 0x17, 0x37, 0xf2, 0x38, 0x76, 0x03, 0xff, 0xda, 0xf1,
-	0x98, 0xe3, 0x6e, 0x36, 0xee, 0x4c, 0xc5, 0x56, 0x11, 0x2e, 0x50, 0xa5, 0x4b, 0xa8, 0x5d, 0x78,
-	0xc1, 0x7d, 0x84, 0xfe, 0x05, 0x70, 0xeb, 0x05, 0xf7, 0xf6, 0x38, 0x58, 0xf8, 0x71, 0xba, 0xd8,
-	0x28, 0xd2, 0xa3, 0x00, 0xfa, 0x0f, 0xd4, 0xa8, 0x10, 0x89, 0x15, 0x76, 0x95, 0x3a, 0x27, 0xe9,
-	0xa7, 0x05, 0xb5, 0xc6, 0x89, 0x4e, 0x6a, 0x40, 0x4d, 0x99, 0xcd, 0xe3, 0x2f, 0x2f, 0xdf, 0x40,
-	0xb7, 0x18, 0x15, 0x35, 0x61, 0xeb, 0x07, 0x5d, 0xd5, 0x84, 0x12, 0x6a, 0x41, 0x6d, 0xa8, 0xc8,
-	0xd7, 0x8a, 0x50, 0x46, 0x00, 0x75, 0x0a, 0x5e, 0x9f, 0x09, 0x95, 0xd3, 0x5f, 0x9b, 0x50, 0x3b,
-	0x3f, 0x37, 0xdd, 0x19, 0x7a, 0x0d, 0x0d, 0x7e, 0x1f, 0xd0, 0x36, 0x2f, 0x81, 0x79, 0x3d, 0x78,
-	0xcc, 0xa5, 0xc2, 0x6d, 0x91, 0x4a, 0xe8, 0x0d, 0xb4, 0x4d, 0x12, 0x67, 0xc3, 0xdb, 0xe1, 0xb4,
-	0x14, 0x38, 0x78, 0x08, 0x48, 0x25, 0xf4, 0x1c, 0xea, 0x03, 0x12, 0xd3, 0x0f, 0x8f, 0x62, 0x88,
-	0xec, 0x85, 0xe1, 0xc5, 0x52, 0x09, 0xbd, 0x02, 0x30, 0x82, 0x7b, 0x12, 0x06, 0xfe, 0x2a, 0x33,
-	0x75, 0x9a, 0x4e, 0x5e, 0x2a, 0xa1, 0x13, 0x68, 0x9b, 0xd3, 0x45, 0x3c, 0x09, 0xee, 0x37, 0xe3,
-	0xff, 0x1f, 0x5a, 0x98, 0xdc, 0x04, 0x41, 0xbc, 0x11, 0xfb, 0x45, 0x92, 0xb2, 0x36, 0x42, 0xab,
-	0x37, 0xe1, 0x20, 0xf7, 0xa2, 0x93, 0x4a, 0xe8, 0x7f, 0xd0, 0x48, 0xa8, 0xd1, 0x03, 0xb7, 0xed,
-	0x25, 0x2d, 0x92, 0x4a, 0xe8, 0x2c, 0x97, 0xf0, 0x7a, 0xbf, 0x6b, 0xf2, 0x38, 0x85, 0x9d, 0xd4,
-	0x48, 0xf6, 0xbc, 0x35, 0x41, 0xd6, 0xd8, 0xbc, 0x83, 0xdd, 0x5c, 0xa0, 0x48, 0xf7, 0x0d, 0x5d,
-	0xcb, 0xc2, 0x2d, 0x5f, 0xae, 0xeb, 0xc3, 0x65, 0x33, 0xd8, 0x38, 0xc5, 0x37, 0xd0, 0xe5, 0x36,
-	0x1b, 0x67, 0xf8, 0x16, 0x84, 0x65, 0x98, 0xbf, 0x94, 0xe0, 0xb7, 0xb0, 0x8d, 0x49, 0x14, 0x3b,
-	0x61, 0xac, 0x38, 0xf3, 0xc0, 0xdb, 0x30, 0xc5, 0x33, 0x68, 0x73, 0xab, 0xfe, 0x74, 0x3c, 0xdf,
-	0xd0, 0xe8, 0x1c, 0xf6, 0x4c, 0x12, 0xeb, 0xfe, 0x82, 0x2d, 0x68, 0xd5, 0x9f, 0xb8, 0x63, 0x87,
-	0x3e, 0x62, 0x68, 0x7f, 0x69, 0x9e, 0xdf, 0xdd, 0x7f, 0xe2, 0xc3, 0x8b, 0xbf, 0xea, 0xa3, 0xb8,
-	0xff, 0xd7, 0xf9, 0x78, 0x05, 0xcd, 0x01, 0x89, 0x93, 0xbd, 0xb1, 0x26, 0xf3, 0xb4, 0xd9, 0x8c,
-	0xc0, 0x7a, 0xbb, 0xd3, 0x9b, 0x3a, 0xfe, 0x1d, 0xa1, 0xeb, 0x2d, 0xf9, 0x1c, 0x45, 0x9c, 0x92,
-	0x5b, 0x78, 0xeb, 0x02, 0x5d, 0xc2, 0x93, 0x01, 0x2b, 0x78, 0xf5, 0xe3, 0x71, 0x4d, 0xdc, 0xa7,
-	0x4b, 0x68, 0x85, 0x2f, 0x95, 0x6e, 0xea, 0xec, 0x3f, 0xce, 0xd9, 0x1f, 0x01, 0x00, 0x00, 0xff,
-	0xff, 0x42, 0x70, 0xbe, 0x17, 0x42, 0x0d, 0x00, 0x00,
+	// 1501 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xcb, 0x6e, 0xdb, 0xc6,
+	0x1a, 0xd6, 0xc5, 0xba, 0xfd, 0xb2, 0x64, 0x7a, 0x12, 0x3b, 0x84, 0xe3, 0x73, 0x22, 0xf0, 0xe4,
+	0x1c, 0x38, 0xc1, 0x89, 0xd3, 0xd8, 0xbd, 0x24, 0x4b, 0x5a, 0xa2, 0x15, 0xd6, 0xf2, 0x50, 0x18,
+	0x4a, 0x0e, 0xb2, 0x22, 0x68, 0x69, 0x6c, 0x13, 0xa0, 0x48, 0x85, 0xa4, 0x6c, 0xe4, 0x01, 0xfa,
+	0x22, 0xdd, 0xf7, 0x39, 0xba, 0xed, 0x13, 0x74, 0xd7, 0xc7, 0x28, 0x50, 0xcc, 0x70, 0x48, 0x91,
+	0x96, 0x52, 0xb8, 0xdd, 0x74, 0x63, 0xcc, 0xff, 0xfd, 0xf7, 0x8b, 0xfe, 0x9f, 0x86, 0x1d, 0x7b,
+	0xee, 0xbc, 0xbe, 0xbc, 0x0c, 0x9d, 0x59, 0xfc, 0xf7, 0x70, 0x1e, 0xf8, 0x91, 0x8f, 0x2a, 0x9c,
+	0xd8, 0x7b, 0x7a, 0xeb, 0xbb, 0xd1, 0x8d, 0x6d, 0x71, 0x30, 0x7c, 0xed, 0xcf, 0xa9, 0xe7, 0xbb,
+	0x51, 0x2c, 0xb3, 0xd7, 0xc9, 0x33, 0x23, 0x3a, 0xb9, 0x61, 0xef, 0x2b, 0xc7, 0xa5, 0xb1, 0x84,
+	0xf2, 0x1d, 0xd4, 0x86, 0x06, 0x1e, 0xfa, 0x41, 0x84, 0xda, 0x50, 0xd2, 0x7b, 0x72, 0xb1, 0x53,
+	0x3c, 0xa8, 0x90, 0x92, 0xde, 0x43, 0xfb, 0xd0, 0x30, 0xe6, 0x34, 0x30, 0x23, 0x3b, 0xa2, 0x72,
+	0xa9, 0x53, 0x3c, 0x68, 0x90, 0x25, 0xc0, 0x14, 0x31, 0xd6, 0xff, 0x86, 0xe2, 0x6f, 0x45, 0x28,
+	0x1b, 0xee, 0xaa, 0x96, 0x02, 0x9b, 0x26, 0x0d, 0x1c, 0xdb, 0xc5, 0x8b, 0xd9, 0x25, 0x0d, 0x84,
+	0x62, 0x0e, 0xcb, 0x5b, 0x2e, 0xdf, 0xb3, 0x8c, 0x9e, 0x43, 0x4b, 0xf7, 0x22, 0x1a, 0x78, 0xb6,
+	0x1b, 0x4b, 0x6c, 0x70, 0x89, 0x3c, 0xc8, 0xfd, 0x0e, 0xe5, 0x1a, 0x67, 0x95, 0xf4, 0x21, 0x7a,
+	0x09, 0x75, 0x91, 0x48, 0x28, 0x57, 0x3a, 0xe5, 0x83, 0xe6, 0x51, 0xfb, 0x30, 0xae, 0xb3, 0x80,
+	0x49, 0xca, 0x67, 0xb2, 0xa2, 0x5a, 0xa1, 0x5c, 0xcd, 0xc9, 0x0a, 0x98, 0xa4, 0x7c, 0xe5, 0x77,
+	0x96, 0x27, 0x1e, 0xff, 0x63, 0x79, 0xee, 0x43, 0x63, 0xe8, 0x7b, 0x2c, 0x16, 0xbd, 0x27, 0x57,
+	0xb8, 0xfb, 0x25, 0xc0, 0xb8, 0xef, 0xef, 0xd4, 0xe9, 0x34, 0xa0, 0x61, 0x28, 0xd7, 0x63, 0x0f,
+	0x29, 0x80, 0x76, 0xa1, 0xca, 0xe4, 0xb0, 0x2f, 0x37, 0xb8, 0xa2, 0xa0, 0x58, 0xfe, 0x21, 0x0d,
+	0x6e, 0x9d, 0x09, 0x0d, 0x65, 0xc8, 0xe5, 0x6f, 0xc6, 0x30, 0x49, 0xf9, 0xca, 0x4f, 0x25, 0xa8,
+	0x09, 0x14, 0x21, 0xd8, 0xc0, 0xf6, 0x8c, 0xf2, 0x2a, 0x34, 0x08, 0x7f, 0xe7, 0x23, 0x28, 0xdd,
+	0x8f, 0xe0, 0x31, 0x54, 0x0c, 0x6f, 0x61, 0x7a, 0x22, 0xfb, 0x98, 0x60, 0x76, 0xcc, 0x91, 0x7d,
+	0xcd, 0x13, 0xae, 0x10, 0xfe, 0x66, 0x58, 0x97, 0x61, 0x71, 0x8a, 0xfc, 0x8d, 0xfe, 0x0d, 0x80,
+	0x29, 0x9d, 0x86, 0x9a, 0x3d, 0xf7, 0x5d, 0xb9, 0xda, 0x29, 0x1e, 0xd4, 0x49, 0x06, 0x61, 0xbe,
+	0x39, 0xd5, 0xbb, 0x99, 0xcc, 0xf9, 0x28, 0xd4, 0xc9, 0x12, 0x48, 0xb9, 0xfa, 0xf5, 0x6c, 0xce,
+	0x6b, 0x93, 0x70, 0x19, 0x80, 0x64, 0xa8, 0xf5, 0xe9, 0x8c, 0x15, 0x44, 0x14, 0x27, 0x21, 0x99,
+	0x57, 0x6e, 0x3e, 0x6e, 0x0a, 0xf0, 0xc0, 0x33, 0x08, 0xb3, 0xcb, 0xec, 0xc7, 0xec, 0x66, 0x9c,
+	0x71, 0x0a, 0x28, 0x36, 0x3c, 0x36, 0xf0, 0x78, 0x14, 0xd8, 0x57, 0x57, 0xce, 0xc4, 0x9c, 0xdc,
+	0xd0, 0xe9, 0xc2, 0xa5, 0x41, 0x88, 0x74, 0xd8, 0x8a, 0x18, 0xb8, 0x84, 0x78, 0x19, 0x9b, 0x47,
+	0xcf, 0x0e, 0x73, 0xbf, 0xe7, 0x15, 0x4d, 0x72, 0x5f, 0x4f, 0x39, 0x80, 0x0d, 0x03, 0x8f, 0x43,
+	0xd4, 0x81, 0x8a, 0x13, 0xd1, 0x19, 0x33, 0xc4, 0x7a, 0x08, 0xa2, 0x87, 0x06, 0x1e, 0x93, 0x98,
+	0xa1, 0x7c, 0x05, 0x75, 0xd1, 0xbb, 0x10, 0x3d, 0xcf, 0x4b, 0xdf, 0xef, 0x78, 0xaa, 0x01, 0x4c,
+	0x9f, 0x7e, 0x5a, 0xd0, 0x30, 0x5a, 0x19, 0xf2, 0xe2, 0xea, 0x90, 0x2b, 0x2f, 0x01, 0x86, 0x06,
+	0x4e, 0x34, 0x32, 0xe3, 0x3a, 0xe5, 0xe2, 0xad, 0xe5, 0xb8, 0x4e, 0x95, 0x5f, 0xcb, 0xd0, 0x50,
+	0x5d, 0x3b, 0x98, 0x8d, 0x3e, 0xcf, 0xa9, 0xf2, 0x4b, 0x19, 0x2a, 0xec, 0x11, 0xa2, 0x1a, 0x94,
+	0x07, 0x86, 0x29, 0x15, 0x50, 0x1b, 0xa0, 0xf7, 0x51, 0xc7, 0x7d, 0xab, 0xaf, 0x9a, 0x43, 0xa9,
+	0x88, 0x5a, 0xd0, 0x30, 0xf0, 0xd8, 0x52, 0x07, 0x2a, 0x39, 0x97, 0x4a, 0xe8, 0x09, 0x3c, 0x62,
+	0xa4, 0x39, 0x52, 0xc9, 0x68, 0x3c, 0xb4, 0x4e, 0x55, 0x7d, 0x30, 0x26, 0x9a, 0x54, 0x46, 0xbb,
+	0x80, 0x38, 0x43, 0xef, 0x63, 0x75, 0x60, 0xf5, 0xb4, 0x3e, 0x51, 0x7b, 0x9a, 0xb4, 0x91, 0x28,
+	0xf4, 0x88, 0x7e, 0x3a, 0xb2, 0x8c, 0x53, 0xeb, 0x83, 0x8e, 0x7b, 0xc6, 0x07, 0xa9, 0x82, 0xf6,
+	0x41, 0x66, 0x8c, 0x81, 0x61, 0x9a, 0x0c, 0x37, 0xce, 0xbb, 0xba, 0xd5, 0x7d, 0xaf, 0x62, 0xac,
+	0x0d, 0xa4, 0x6a, 0xea, 0x87, 0x9b, 0x33, 0x53, 0x3f, 0x35, 0xf4, 0x02, 0xfe, 0xcb, 0x18, 0x23,
+	0xa2, 0x62, 0xf3, 0x5c, 0x37, 0x4d, 0xdd, 0xc0, 0x96, 0x8e, 0x47, 0x1a, 0x39, 0xd5, 0x88, 0x86,
+	0xbb, 0x9a, 0xf5, 0x41, 0x25, 0x58, 0xc7, 0x7d, 0xa9, 0x8e, 0xf6, 0x60, 0x97, 0x87, 0xde, 0x1d,
+	0xe9, 0x17, 0xea, 0x88, 0x09, 0x26, 0x66, 0x1a, 0x48, 0xe6, 0x43, 0x62, 0x0d, 0x89, 0xd1, 0xd5,
+	0x4c, 0x93, 0xe5, 0xab, 0x11, 0x62, 0x10, 0x09, 0x50, 0x07, 0xf6, 0xb3, 0x71, 0x9d, 0x69, 0x1f,
+	0x2d, 0xf3, 0x23, 0xee, 0xa6, 0xba, 0x4d, 0xb4, 0x03, 0xdb, 0x4c, 0x42, 0x1f, 0x8d, 0xad, 0xa1,
+	0x81, 0x59, 0x2d, 0x46, 0xa6, 0xb4, 0x89, 0xb6, 0xa1, 0x95, 0x56, 0x8a, 0xa9, 0x4b, 0xad, 0xfb,
+	0xd0, 0x89, 0xd4, 0x4e, 0x12, 0x4b, 0xa0, 0x61, 0xd7, 0x62, 0x59, 0x48, 0x5b, 0x49, 0x3d, 0x72,
+	0x8c, 0xae, 0x88, 0x4a, 0x42, 0x08, 0xda, 0x59, 0xee, 0xa9, 0x2e, 0x6d, 0xa3, 0x47, 0xb0, 0x95,
+	0xc5, 0xd4, 0x73, 0x5d, 0x42, 0xca, 0x5b, 0x68, 0xf3, 0xfe, 0x0e, 0xed, 0xc0, 0x9e, 0xd1, 0x88,
+	0x06, 0x48, 0x82, 0xf2, 0x19, 0xfd, 0x2c, 0x26, 0x87, 0x3d, 0xd9, 0x4e, 0xb8, 0xb0, 0xdd, 0x45,
+	0x72, 0x53, 0x62, 0x42, 0xf9, 0xb1, 0xc8, 0xed, 0x71, 0xed, 0xcc, 0x30, 0xa5, 0xd3, 0x22, 0x2c,
+	0x2c, 0x81, 0x07, 0x6d, 0xe0, 0x5d, 0xa8, 0xb2, 0x9f, 0xe5, 0x22, 0x14, 0x0b, 0x48, 0x50, 0xe8,
+	0x1b, 0x80, 0x34, 0xc4, 0x50, 0xde, 0xe0, 0xbf, 0x88, 0x1d, 0xf1, 0x8b, 0xc8, 0x27, 0x40, 0x32,
+	0x82, 0xca, 0x27, 0xd8, 0x32, 0x06, 0xa3, 0x5c, 0x8c, 0x1d, 0x68, 0xf2, 0x85, 0x7d, 0x65, 0x4f,
+	0xa8, 0x38, 0x10, 0x2d, 0x92, 0x85, 0xd2, 0x3d, 0xcf, 0x48, 0x9e, 0x49, 0x29, 0xb3, 0xe7, 0x13,
+	0xf0, 0x4b, 0x91, 0x2a, 0x3f, 0x14, 0xa1, 0x75, 0x41, 0x83, 0xd0, 0xf1, 0x3d, 0x91, 0x93, 0x0c,
+	0xb5, 0xdb, 0x18, 0x10, 0x35, 0x49, 0x48, 0x56, 0xaf, 0xcb, 0x85, 0xe3, 0x4e, 0x47, 0xce, 0x2c,
+	0xbd, 0xd8, 0x29, 0xc0, 0xf6, 0xda, 0xc4, 0x9f, 0xcd, 0x9c, 0xe8, 0xbd, 0x1d, 0xde, 0x08, 0x2f,
+	0x19, 0x84, 0x69, 0x5f, 0x3b, 0x91, 0x08, 0x22, 0xbe, 0x45, 0x4b, 0x40, 0x79, 0x0b, 0xf5, 0x81,
+	0x7f, 0x3d, 0xa0, 0xb7, 0xd4, 0x65, 0x1d, 0x74, 0xd9, 0x43, 0xf8, 0x8f, 0x09, 0x96, 0xc1, 0xc4,
+	0x76, 0x5d, 0xd1, 0x89, 0x3a, 0x11, 0x94, 0xa2, 0x41, 0x9d, 0xd0, 0x70, 0xee, 0x7b, 0x21, 0x45,
+	0xcf, 0xa0, 0x19, 0x72, 0x7b, 0xd6, 0xc4, 0x9f, 0x52, 0x71, 0x4e, 0x21, 0x86, 0xba, 0xfe, 0x94,
+	0xb2, 0xe4, 0x66, 0x34, 0x0c, 0xed, 0xeb, 0x24, 0x81, 0x84, 0x54, 0x42, 0x68, 0xb2, 0xc5, 0x9d,
+	0xd4, 0xfd, 0x05, 0x54, 0x0d, 0x6f, 0x41, 0xe8, 0x27, 0xb1, 0x46, 0xb7, 0x33, 0xdb, 0x2f, 0x16,
+	0x21, 0x42, 0x00, 0xbd, 0x83, 0x4d, 0x73, 0x71, 0xa9, 0x4e, 0x22, 0xc7, 0xf7, 0x2e, 0x6c, 0x97,
+	0x1b, 0x6e, 0xa7, 0xed, 0x4e, 0x59, 0x7c, 0x15, 0x91, 0x9c, 0xa8, 0x72, 0x06, 0x95, 0x53, 0xd7,
+	0xbf, 0x0b, 0xd1, 0xbf, 0x00, 0xae, 0x5c, 0xff, 0xce, 0x9a, 0xf8, 0x0b, 0x2f, 0x4a, 0x16, 0x1b,
+	0x43, 0xba, 0x0c, 0x40, 0xff, 0x81, 0x0a, 0x23, 0xd8, 0x05, 0x64, 0xa3, 0xd4, 0x3a, 0x4c, 0x3e,
+	0xe0, 0x98, 0x36, 0x89, 0x79, 0x4a, 0x0d, 0x2a, 0xda, 0x6c, 0x1e, 0x7d, 0x7e, 0xf9, 0x06, 0xda,
+	0x79, 0xaf, 0xa8, 0x0e, 0x1b, 0xdf, 0x1b, 0x3a, 0x96, 0x0a, 0xa8, 0x01, 0x95, 0x81, 0xa6, 0x5e,
+	0x68, 0x52, 0x11, 0x01, 0x54, 0x19, 0x78, 0x71, 0x2c, 0x95, 0x8e, 0x7e, 0x6e, 0x40, 0xe5, 0xe4,
+	0xc4, 0x74, 0x66, 0xe8, 0x35, 0xd4, 0xc4, 0x3c, 0xa0, 0x4d, 0x91, 0x02, 0xb7, 0xba, 0xf7, 0x58,
+	0x50, 0xb9, 0x69, 0x51, 0x0a, 0xe8, 0x0d, 0x34, 0x4d, 0x1a, 0xa5, 0xcd, 0xdb, 0x12, 0x62, 0x09,
+	0xb0, 0x77, 0x1f, 0x50, 0x0a, 0xe8, 0x39, 0x54, 0xfb, 0x34, 0x62, 0x9f, 0x77, 0x79, 0x17, 0xe9,
+	0x89, 0x71, 0x23, 0xa5, 0x80, 0x5e, 0x01, 0x0c, 0xfd, 0x3b, 0x1a, 0xf8, 0xde, 0xaa, 0x64, 0x62,
+	0x34, 0xe9, 0xbc, 0x52, 0x40, 0x87, 0xd0, 0x34, 0x6f, 0x16, 0xd1, 0xd4, 0xbf, 0x7b, 0x98, 0xfc,
+	0xff, 0xa1, 0x41, 0xe8, 0xa5, 0xef, 0x47, 0x0f, 0x92, 0x7e, 0x11, 0x87, 0x8c, 0xc7, 0x68, 0x75,
+	0x12, 0xf6, 0x32, 0xa7, 0x51, 0x29, 0xa0, 0xff, 0xb1, 0xd3, 0x1f, 0xf1, 0x13, 0x9a, 0x37, 0xdb,
+	0x5c, 0x8a, 0x85, 0x71, 0xc0, 0x7d, 0x1a, 0xa5, 0x07, 0x74, 0x7d, 0x08, 0x09, 0x5b, 0x29, 0xa0,
+	0x6f, 0xa1, 0xcd, 0xec, 0x7a, 0x8b, 0x54, 0x65, 0x4d, 0x28, 0x6b, 0xf4, 0x8e, 0x33, 0x85, 0x59,
+	0x1f, 0xff, 0x9a, 0x7c, 0x8f, 0x60, 0x2b, 0x51, 0x52, 0x5d, 0x77, 0x4d, 0x32, 0x6b, 0x74, 0xde,
+	0xc1, 0x76, 0xc6, 0x51, 0x68, 0x78, 0x43, 0x03, 0xa7, 0xee, 0x96, 0x47, 0x7c, 0xbd, 0xbb, 0xb4,
+	0xd7, 0x0f, 0x0e, 0xf1, 0x0d, 0xb4, 0x85, 0xce, 0x83, 0x23, 0x7c, 0x0b, 0xd2, 0xd2, 0xcd, 0x5f,
+	0x0a, 0xf0, 0x6b, 0xd8, 0x24, 0x34, 0x8c, 0xec, 0x20, 0x8a, 0xbf, 0x0d, 0x1f, 0x16, 0xe2, 0x31,
+	0x34, 0x85, 0x16, 0xff, 0x64, 0x7c, 0x98, 0xd2, 0x09, 0xec, 0x98, 0xbc, 0xcf, 0xfc, 0x10, 0xe8,
+	0xde, 0xd4, 0x99, 0xd8, 0xec, 0xa7, 0x8c, 0x76, 0x97, 0xea, 0xd9, 0x1b, 0xf1, 0x27, 0x36, 0xdc,
+	0xe8, 0x8b, 0x36, 0xf2, 0x77, 0x66, 0x9d, 0x8d, 0x57, 0x50, 0xef, 0xd3, 0x28, 0xde, 0x4f, 0x6b,
+	0x22, 0x4f, 0x8a, 0xcd, 0x05, 0x78, 0x6d, 0xb7, 0xba, 0x37, 0xb6, 0x77, 0x4d, 0xd9, 0x1a, 0x8d,
+	0x3f, 0x65, 0x91, 0x10, 0xc9, 0x2c, 0xd6, 0x75, 0x8e, 0xce, 0xe0, 0x49, 0x3c, 0xd8, 0xab, 0x9f,
+	0xb5, 0x6b, 0xfc, 0x3e, 0x5d, 0x42, 0x2b, 0xf2, 0x4a, 0xe1, 0xb2, 0xca, 0xff, 0x63, 0x3d, 0xfe,
+	0x23, 0x00, 0x00, 0xff, 0xff, 0xdb, 0xfb, 0x7c, 0xc5, 0x10, 0x0f, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -1198,6 +1359,10 @@
 	GetONU(ctx context.Context, in *ONURequest, opts ...grpc.CallOption) (*ONU, error)
 	// Get status of all ONUs
 	GetONUs(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ONUs, error)
+	// Get all the Services
+	GetServices(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Services, error)
+	// Get all the Services of an ONU by serial number
+	GetOnuServices(ctx context.Context, in *ONURequest, opts ...grpc.CallOption) (*Services, error)
 	// Shutdown an ONU by serial number
 	ShutdownONU(ctx context.Context, in *ONURequest, opts ...grpc.CallOption) (*Response, error)
 	// Shutdown all ONUs in OLT
@@ -1306,6 +1471,24 @@
 	return out, nil
 }
 
+func (c *bBSimClient) GetServices(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Services, error) {
+	out := new(Services)
+	err := c.cc.Invoke(ctx, "/bbsim.BBSim/GetServices", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *bBSimClient) GetOnuServices(ctx context.Context, in *ONURequest, opts ...grpc.CallOption) (*Services, error) {
+	out := new(Services)
+	err := c.cc.Invoke(ctx, "/bbsim.BBSim/GetOnuServices", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *bBSimClient) ShutdownONU(ctx context.Context, in *ONURequest, opts ...grpc.CallOption) (*Response, error) {
 	out := new(Response)
 	err := c.cc.Invoke(ctx, "/bbsim.BBSim/ShutdownONU", in, out, opts...)
@@ -1441,6 +1624,10 @@
 	GetONU(context.Context, *ONURequest) (*ONU, error)
 	// Get status of all ONUs
 	GetONUs(context.Context, *Empty) (*ONUs, error)
+	// Get all the Services
+	GetServices(context.Context, *Empty) (*Services, error)
+	// Get all the Services of an ONU by serial number
+	GetOnuServices(context.Context, *ONURequest) (*Services, error)
 	// Shutdown an ONU by serial number
 	ShutdownONU(context.Context, *ONURequest) (*Response, error)
 	// Shutdown all ONUs in OLT
@@ -1497,6 +1684,12 @@
 func (*UnimplementedBBSimServer) GetONUs(ctx context.Context, req *Empty) (*ONUs, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method GetONUs not implemented")
 }
+func (*UnimplementedBBSimServer) GetServices(ctx context.Context, req *Empty) (*Services, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetServices not implemented")
+}
+func (*UnimplementedBBSimServer) GetOnuServices(ctx context.Context, req *ONURequest) (*Services, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetOnuServices not implemented")
+}
 func (*UnimplementedBBSimServer) ShutdownONU(ctx context.Context, req *ONURequest) (*Response, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method ShutdownONU not implemented")
 }
@@ -1685,6 +1878,42 @@
 	return interceptor(ctx, in, info, handler)
 }
 
+func _BBSim_GetServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(Empty)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BBSimServer).GetServices(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/bbsim.BBSim/GetServices",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BBSimServer).GetServices(ctx, req.(*Empty))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _BBSim_GetOnuServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ONURequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BBSimServer).GetOnuServices(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/bbsim.BBSim/GetOnuServices",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BBSimServer).GetOnuServices(ctx, req.(*ONURequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _BBSim_ShutdownONU_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 	in := new(ONURequest)
 	if err := dec(in); err != nil {
@@ -1956,6 +2185,14 @@
 			Handler:    _BBSim_GetONUs_Handler,
 		},
 		{
+			MethodName: "GetServices",
+			Handler:    _BBSim_GetServices_Handler,
+		},
+		{
+			MethodName: "GetOnuServices",
+			Handler:    _BBSim_GetOnuServices_Handler,
+		},
+		{
 			MethodName: "ShutdownONU",
 			Handler:    _BBSim_ShutdownONU_Handler,
 		},
diff --git a/api/bbsim/bbsim.proto b/api/bbsim/bbsim.proto
index c079968..ce4abe3 100644
--- a/api/bbsim/bbsim.proto
+++ b/api/bbsim/bbsim.proto
@@ -45,10 +45,23 @@
     string OperState = 3;
     string InternalState = 4;
     int32 PonPortID = 5;
-    int32 STag = 6;
-    int32 CTag = 7;
     string HwAddress = 8;
     int32 PortNo = 9;
+    repeated Service services = 10;
+}
+
+message Service {
+    string Name = 1;
+    string HwAddress = 2;
+    string OnuSn = 3;
+    int32 STag = 4;
+    int32 CTag = 5;
+    bool NeedsEapol = 6;
+    bool NeedsDhcp = 7;
+    bool NeedsIgmp = 8;
+    int32 GemPort = 9;
+    string EapolState = 10;
+    string DhcpState = 11;
 }
 
 message ONUTrafficSchedulers {
@@ -59,6 +72,10 @@
     repeated ONU items = 1;
 }
 
+message Services {
+    repeated Service items = 1;
+}
+
 // Inputs
 
 message ONURequest {
@@ -109,17 +126,17 @@
 // plus an optional list of AlarmParameter list that can be used
 // to set additional fields in alarms that support them.
 message ONUAlarmRequest {
-    string AlarmType = 1;                   // name of alarm to raise
-    string SerialNumber = 2;                // serial number of ONU
-    string Status = 3;                      // status of Alarm
+    string AlarmType = 1; // name of alarm to raise
+    string SerialNumber = 2; // serial number of ONU
+    string Status = 3; // status of Alarm
     repeated AlarmParameter Parameters = 4; // optional list of additional parameters
 }
 
 // OLT alarm request
 message OLTAlarmRequest {
-    uint32 InterfaceID = 1;                 // Switch Interface Id
-    string InterfaceType = 2;                // PON or NNI Type
-    string Status = 3;                      // Interface Operstatus
+    uint32 InterfaceID = 1; // Switch Interface Id
+    string InterfaceType = 2; // PON or NNI Type
+    string Status = 3; // Interface Operstatus
 }
 
 // Utils
@@ -148,8 +165,8 @@
 }
 
 message IgmpRequest {
-	ONURequest OnuReq = 1;
-	SubActionTypes SubActionVal = 2;
+    ONURequest OnuReq = 1;
+    SubActionTypes SubActionVal = 2;
 }
 
 message Flows {
@@ -157,54 +174,84 @@
     repeated openolt.Flow flows = 2;
 }
 
-message Empty {}
+message Empty {
+}
 
 service BBSim {
     // Get BBSim version
-    rpc Version(Empty) returns (VersionNumber) {}
+    rpc Version (Empty) returns (VersionNumber) {
+    }
     // Set BBSim log level
-    rpc SetLogLevel(LogLevel) returns (LogLevel) {}
+    rpc SetLogLevel (LogLevel) returns (LogLevel) {
+    }
 
     // Get current status of OLT
-    rpc GetOlt(Empty) returns (Olt) {}
+    rpc GetOlt (Empty) returns (Olt) {
+    }
     // Poweron OLT
-    rpc PoweronOlt(Empty) returns (Response) {}
+    rpc PoweronOlt (Empty) returns (Response) {
+    }
     // Shutdown OLT
-    rpc ShutdownOlt(Empty) returns (Response) {}
+    rpc ShutdownOlt (Empty) returns (Response) {
+    }
     // Reboot OLT
-    rpc RebootOlt(Empty) returns (Response) {}
+    rpc RebootOlt (Empty) returns (Response) {
+    }
 
     // Get status of an ONU by serial number
-    rpc GetONU(ONURequest) returns (ONU) {}
+    rpc GetONU (ONURequest) returns (ONU) {
+    }
     // Get status of all ONUs
-    rpc GetONUs(Empty) returns (ONUs) {}
+    rpc GetONUs (Empty) returns (ONUs) {
+    }
+
+    // Get all the Services
+    rpc GetServices (Empty) returns (Services) {
+    }
+
+    // Get all the Services of an ONU by serial number
+    rpc GetOnuServices (ONURequest) returns (Services) {
+    }
 
     // Shutdown an ONU by serial number
-    rpc ShutdownONU (ONURequest) returns (Response) {}
+    rpc ShutdownONU (ONURequest) returns (Response) {
+    }
     // Shutdown all ONUs in OLT
-    rpc ShutdownAllONUs (Empty) returns (Response) {}
+    rpc ShutdownAllONUs (Empty) returns (Response) {
+    }
     // Shutdown all ONUs under a PON by pon-port-ID
-    rpc ShutdownONUsOnPON(PONRequest) returns (Response) {}
+    rpc ShutdownONUsOnPON (PONRequest) returns (Response) {
+    }
 
     // Poweron an ONU by serial number 
-    rpc PoweronONU (ONURequest) returns (Response) {}
+    rpc PoweronONU (ONURequest) returns (Response) {
+    }
     // Poweron all ONUs in OLT
-    rpc PoweronAllONUs (Empty) returns (Response) {}
+    rpc PoweronAllONUs (Empty) returns (Response) {
+    }
     // Poweron all ONUs under a PON by pon-port-ID
-    rpc PoweronONUsOnPON(PONRequest) returns (Response) {}
+    rpc PoweronONUsOnPON (PONRequest) returns (Response) {
+    }
 
     // Restart EAPOL for ONU
-    rpc RestartEapol (ONURequest) returns (Response) {}
+    rpc RestartEapol (ONURequest) returns (Response) {
+    }
     // Resatrt DHCP for ONU
-    rpc RestartDhcp (ONURequest) returns (Response) {}
+    rpc RestartDhcp (ONURequest) returns (Response) {
+    }
     // Send ONU alarm indication
-    rpc SetOnuAlarmIndication (ONUAlarmRequest) returns (Response) {}
+    rpc SetOnuAlarmIndication (ONUAlarmRequest) returns (Response) {
+    }
     // Send OLT alarm indication for Interface type NNI or PON
-    rpc SetOltAlarmIndication (OLTAlarmRequest) returns (Response) {}
+    rpc SetOltAlarmIndication (OLTAlarmRequest) returns (Response) {
+    }
     // Get all flows or ONU specific flows
-    rpc GetFlows(ONURequest) returns(Flows) {}
+    rpc GetFlows (ONURequest) returns (Flows) {
+    }
     // Chnage IGMP state 
-    rpc ChangeIgmpState (IgmpRequest) returns (Response) {}
+    rpc ChangeIgmpState (IgmpRequest) returns (Response) {
+    }
     // Get Traffic scheduler information for ONU
-    rpc GetOnuTrafficSchedulers (ONURequest) returns (ONUTrafficSchedulers) {}
+    rpc GetOnuTrafficSchedulers (ONURequest) returns (ONUTrafficSchedulers) {
+    }
 }
diff --git a/build/package/Dockerfile b/build/package/Dockerfile
index 40e410b..ae3b919 100644
--- a/build/package/Dockerfile
+++ b/build/package/Dockerfile
@@ -64,6 +64,6 @@
 RUN mv /usr/sbin/tcpdump /usr/bin/tcpdump
 RUN chmod a+x /app/bbsim
 RUN chmod a+x /usr/bin/bbsimctl
+COPY ./configs/ ./configs/
 RUN bbsimctl completion bash >> "$HOME/.bashrc"
-COPY ./configs/bbsim.yaml ./configs/bbsim.yaml
 CMD [ "/app/bbsim" ]
diff --git a/cmd/bbr/bbr.go b/cmd/bbr/bbr.go
index 5bbfaf7..1cd33bc 100644
--- a/cmd/bbr/bbr.go
+++ b/cmd/bbr/bbr.go
@@ -34,12 +34,12 @@
 
 // usage
 func main() {
-	options := common.GetBBROpts()
+	config := common.GetBBROpts()
 
-	common.SetLogLevel(log.StandardLogger(), options.BBR.LogLevel, options.BBR.LogCaller)
+	common.SetLogLevel(log.StandardLogger(), config.BBR.LogLevel, config.BBR.LogCaller)
 
-	if options.LogFile != "" {
-		file, err := os.OpenFile(options.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+	if config.LogFile != "" {
+		file, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 		if err == nil {
 			log.StandardLogger().Out = file
 		} else {
@@ -47,10 +47,10 @@
 		}
 	}
 
-	if *options.BBSim.CpuProfile != "" {
+	if *config.BBSim.CpuProfile != "" {
 		// start profiling
-		log.Infof("Creating profile file at: %s", *options.BBSim.CpuProfile)
-		f, err := os.Create(*options.BBSim.CpuProfile)
+		log.Infof("Creating profile file at: %s", *config.BBSim.CpuProfile)
+		f, err := os.Create(*config.BBSim.CpuProfile)
 		if err != nil {
 			log.Fatal(err)
 		}
@@ -58,31 +58,32 @@
 	}
 
 	log.WithFields(log.Fields{
-		"OltID":        options.Olt.ID,
-		"NumNniPerOlt": options.Olt.NniPorts,
-		"NumPonPerOlt": options.Olt.PonPorts,
-		"NumOnuPerPon": options.Olt.OnusPonPort,
-		"BBSimIp":      options.BBSimIp,
-		"BBSimPort":    options.BBSimPort,
-		"LogLevel":     options.BBR.LogLevel,
-		"TotalOnus":    options.Olt.PonPorts * options.Olt.OnusPonPort,
+		"OltID":        config.Olt.ID,
+		"NumNniPerOlt": config.Olt.NniPorts,
+		"NumPonPerOlt": config.Olt.PonPorts,
+		"NumOnuPerPon": config.Olt.OnusPonPort,
+		"BBSimIp":      config.BBSimIp,
+		"BBSimPort":    config.BBSimPort,
+		"LogLevel":     config.BBR.LogLevel,
+		"TotalOnus":    config.Olt.PonPorts * config.Olt.OnusPonPort,
 	}).Info("BroadBand Reflector is on")
 
 	// create the OLT device
 	olt := devices.CreateOLT(
-		*options.BBSimYamlConfig,
+		*config.GlobalConfig,
+		common.Services,
 		true,
 	)
 
-	onuIdMap := make(map[uint32]uint32, options.Olt.PonPorts)
+	onuIdMap := make(map[uint32]uint32, config.Olt.PonPorts)
 
 	oltMock := bbrdevices.OltMock{
 		Olt:           olt,
-		TargetOnus:    int(options.Olt.PonPorts * options.Olt.OnusPonPort),
+		TargetOnus:    int(config.Olt.PonPorts * config.Olt.OnusPonPort),
 		CompletedOnus: 0,
-		BBSimIp:       options.BBSimIp,
-		BBSimPort:     options.BBSimPort,
-		BBSimApiPort:  options.BBSimApiPort,
+		BBSimIp:       config.BBSimIp,
+		BBSimPort:     config.BBSimPort,
+		BBSimApiPort:  config.BBSimApiPort,
 		LastUsedOnuId: onuIdMap,
 	}
 
diff --git a/cmd/bbsim/bbsim.go b/cmd/bbsim/bbsim.go
index e7618f9..75c7263 100644
--- a/cmd/bbsim/bbsim.go
+++ b/cmd/bbsim/bbsim.go
@@ -40,7 +40,7 @@
 )
 
 func startApiServer(apiDoneChannel chan bool, group *sync.WaitGroup) {
-	address := common.Options.BBSim.ApiAddress
+	address := common.Config.BBSim.ApiAddress
 	log.Debugf("APIServer listening on %v", address)
 	lis, err := net.Listen("tcp", address)
 	if err != nil {
@@ -70,7 +70,7 @@
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
-	address := common.Options.BBSim.RestApiAddress
+	address := common.Config.BBSim.RestApiAddress
 
 	mux := runtime.NewServeMux()
 	opts := []grpc.DialOption{grpc.WithInsecure()}
@@ -101,8 +101,8 @@
 
 // This server aims to provide compatibility with the previous BBSim version. It is deprecated and will be removed in the future.
 func startLegacyApiServer(apiDoneChannel chan bool, group *sync.WaitGroup) {
-	grpcAddress := common.Options.BBSim.LegacyApiAddress
-	restAddress := common.Options.BBSim.LegacyRestApiAddress
+	grpcAddress := common.Config.BBSim.LegacyApiAddress
+	restAddress := common.Config.BBSim.LegacyRestApiAddress
 
 	log.Debugf("Legacy APIServer listening on %v", grpcAddress)
 	listener, err := net.Listen("tcp", grpcAddress)
@@ -129,15 +129,14 @@
 
 func main() {
 
-	options := common.GetBBSimOpts()
+	common.LoadConfig()
 
-	common.SetLogLevel(log.StandardLogger(), options.BBSim.LogLevel, options.BBSim.LogCaller)
-	log.Tracef("BBSim options: %+v", options)
+	common.SetLogLevel(log.StandardLogger(), common.Config.BBSim.LogLevel, common.Config.BBSim.LogCaller)
 
-	if *options.BBSim.CpuProfile != "" {
+	if *common.Config.BBSim.CpuProfile != "" {
 		// start profiling
-		log.Infof("Creating profile file at: %s", *options.BBSim.CpuProfile)
-		f, err := os.Create(*options.BBSim.CpuProfile)
+		log.Infof("Creating profile file at: %s", *common.Config.BBSim.CpuProfile)
+		f, err := os.Create(*common.Config.BBSim.CpuProfile)
 		if err != nil {
 			log.Fatal(err)
 		}
@@ -145,37 +144,30 @@
 	}
 
 	log.WithFields(log.Fields{
-		"OltID":                options.Olt.ID,
-		"NumNniPerOlt":         options.Olt.NniPorts,
-		"NumPonPerOlt":         options.Olt.PonPorts,
-		"NumOnuPerPon":         options.Olt.OnusPonPort,
-		"TotalOnus":            options.Olt.PonPorts * options.Olt.OnusPonPort,
-		"EnableAuth":           options.BBSim.EnableAuth,
-		"Dhcp":                 options.BBSim.EnableDhcp,
-		"Igmp":                 options.BBSim.EnableIgmp,
-		"Delay":                options.BBSim.Delay,
-		"Events":               options.BBSim.Events,
-		"KafkaEventTopic":      options.BBSim.KafkaEventTopic,
-		"ControlledActivation": options.BBSim.ControlledActivation,
-		"EnablePerf":           options.BBSim.EnablePerf,
-		"CTag":                 options.BBSim.CTag,
-		"CTagAllocation":       options.BBSim.CTagAllocation,
-		"STag":                 options.BBSim.STag,
-		"STagAllocation":       options.BBSim.STagAllocation,
-		"SadisFormat":          options.BBSim.SadisFormat,
-		"DhcpRetry":            options.BBSim.DhcpRetry,
-		"AuthRetry":            options.BBSim.AuthRetry,
+		"OltID":                common.Config.Olt.ID,
+		"NumNniPerOlt":         common.Config.Olt.NniPorts,
+		"NumPonPerOlt":         common.Config.Olt.PonPorts,
+		"NumOnuPerPon":         common.Config.Olt.OnusPonPort,
+		"TotalOnus":            common.Config.Olt.PonPorts * common.Config.Olt.OnusPonPort,
+		"Delay":                common.Config.BBSim.Delay,
+		"Events":               common.Config.BBSim.Events,
+		"KafkaEventTopic":      common.Config.BBSim.KafkaEventTopic,
+		"ControlledActivation": common.Config.BBSim.ControlledActivation,
+		"EnablePerf":           common.Config.BBSim.EnablePerf,
+		"DhcpRetry":            common.Config.BBSim.DhcpRetry,
+		"AuthRetry":            common.Config.BBSim.AuthRetry,
 	}).Info("BroadBand Simulator is on")
 
 	// control channels, they are only closed when the goroutine needs to be terminated
 	apiDoneChannel := make(chan bool)
 
 	olt := devices.CreateOLT(
-		*options,
+		*common.Config,
+		common.Services,
 		false,
 	)
 
-	log.Debugf("Created OLT with id: %d", options.Olt.ID)
+	log.Debugf("Created OLT with id: %d", common.Config.Olt.ID)
 
 	sigs := make(chan os.Signal, 1)
 	// stop API servers on SIGTERM
@@ -192,12 +184,12 @@
 	go startApiServer(apiDoneChannel, &wg)
 	go startLegacyApiServer(apiDoneChannel, &wg)
 	log.Debugf("Started APIService")
-	if common.Options.BBSim.SadisServer {
+	if common.Config.BBSim.SadisServer {
 		wg.Add(1)
 		go sadis.StartRestServer(olt, &wg)
 	}
 
-	if options.BBSim.Events {
+	if common.Config.BBSim.Events {
 		// initialize a publisher
 		if err := common.InitializePublisher(sarama.NewAsyncProducer, olt.ID); err == nil {
 			// start a go routine which will read from channel and publish on kafka
@@ -211,7 +203,7 @@
 
 	defer func() {
 		log.Info("BroadBand Simulator is off")
-		if *options.BBSim.CpuProfile != "" {
+		if *common.Config.BBSim.CpuProfile != "" {
 			log.Info("Stopping profiler")
 			pprof.StopCPUProfile()
 		}
diff --git a/cmd/bbsimctl/bbsimctl.go b/cmd/bbsimctl/bbsimctl.go
index cc05835..08e7b5b 100644
--- a/cmd/bbsimctl/bbsimctl.go
+++ b/cmd/bbsimctl/bbsimctl.go
@@ -38,6 +38,7 @@
 	commands.RegisterConfigCommands(parser)
 	commands.RegisterOltCommands(parser)
 	commands.RegisterONUCommands(parser)
+	commands.RegisterServiceCommands(parser)
 	commands.RegisterPonCommands(parser)
 	commands.RegisterCompletionCommands(parser)
 	commands.RegisterLoggingCommands(parser)
@@ -53,13 +54,6 @@
 			}
 		}
 
-		//corderror, ok := err.(corderrors.CordCtlError)
-		//if ok {
-		//	if corderror.ShouldDumpStack() || config.GlobalOptions.Debug {
-		//		os.Stderr.WriteString("\n" + corderror.Stack())
-		//	}
-		//}
-
 		fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err.Error())
 
 		os.Exit(1)
diff --git a/configs/att-services.yaml b/configs/att-services.yaml
new file mode 100644
index 0000000..c9116e2
--- /dev/null
+++ b/configs/att-services.yaml
@@ -0,0 +1,13 @@
+# Contains a description of the services that needs to be created for each UNI and the corresponding tagging scheme
+
+# ATT
+workflow: att
+services:
+- name: hsia
+  c_tag: 900
+  c_tag_allocation: unique
+  s_tag: 900
+  s_tag_allocation: shared
+  needs_eapol: true
+  needs_dhcp: true
+  tp_id: 64
diff --git a/configs/bbsim.yaml b/configs/bbsim.yaml
index 9600659..2f4322f 100644
--- a/configs/bbsim.yaml
+++ b/configs/bbsim.yaml
@@ -23,10 +23,6 @@
   # log_level: "debug"
   # log_caller: false
   # delay: 200
-  # c_tag_allocation: unique
-  # c_tag: 900
-  # s_tag_allocation: shared
-  # s_tag: 900
   # kafka_event_topic: ""
 
 # OLT device settings
diff --git a/configs/dt-services.yaml b/configs/dt-services.yaml
new file mode 100644
index 0000000..fd53fd9
--- /dev/null
+++ b/configs/dt-services.yaml
@@ -0,0 +1,12 @@
+# Contains a description of the services that needs to be created for each UNI and the corresponding tagging scheme
+
+# ATT
+workflow: att
+services:
+- name: hsia
+  uni_tag_match: 4096
+  c_tag: 7
+  c_tag_allocation: shared
+  s_tag: 20
+  s_tag_allocation: unique
+  tp_id: 64
\ No newline at end of file
diff --git a/configs/tt-services.yaml b/configs/tt-services.yaml
new file mode 100644
index 0000000..aa6c661
--- /dev/null
+++ b/configs/tt-services.yaml
@@ -0,0 +1,48 @@
+# Contains a description of the services that needs to be created for each UNI and the corresponding tagging scheme
+
+# TT
+workflow: tt
+services:
+- name: hsia
+  uni_tag_match: 35
+  c_tag: 900
+  c_tag_allocation: unique
+  s_tag: 900
+  s_tag_allocation: shared
+  tp_id: 64
+- name: voip
+  uni_tag_match: 65
+  c_tag: 444
+  c_tag_allocation: shared
+  s_tag: 333
+  s_tag_allocation: shared
+  needs_dhcp: true
+  tp_id: 65
+  configure_mac_address: true
+  us_pon_c_tag_priority: 7
+  us_pon_s_tag_priority: 7
+  ds_pon_c_tag_priority: 7
+  ds_pon_s_tag_priority: 7
+- name: vod
+  uni_tag_match: 55
+  c_tag: 55
+  c_tag_allocation: shared
+  s_tag: 555
+  s_tag_allocation: shared
+  needs_dhcp: true
+  needs_igmp: true
+  tp_id: 66
+  configure_mac_address: true
+  us_pon_c_tag_priority: 5
+  us_pon_s_tag_priority: 5
+  ds_pon_c_tag_priority: 5
+  ds_pon_s_tag_priority: 5
+# NOTE: the multicast service must be called MC unless you set multicastServiceName to something else in org.opencord.olt.impl.Olt
+- name: MC
+  c_tag: 55
+  c_tag_allocation: shared
+  s_tag: 555
+  s_tag_allocation: shared
+  tp_id: 66
+  ds_pon_c_tag_priority: 5
+  ds_pon_s_tag_priority: 5
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 2fd2603..f483e0d 100644
--- a/go.mod
+++ b/go.mod
@@ -7,11 +7,11 @@
 	github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
 	github.com/cboling/omci v0.1.0
 	github.com/deckarep/golang-set v1.7.1 // indirect
-	github.com/ghodss/yaml v1.0.0
 	github.com/golang/protobuf v1.3.2
 	github.com/google/gopacket v1.1.17
 	github.com/gorilla/mux v1.7.3
 	github.com/grpc-ecosystem/grpc-gateway v1.12.2
+	github.com/imdario/mergo v0.3.11
 	github.com/jessevdk/go-flags v1.4.0
 	github.com/jhump/protoreflect v1.5.0
 	github.com/jpillora/backoff v1.0.0
@@ -24,6 +24,6 @@
 	github.com/sirupsen/logrus v1.4.2
 	google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c
 	google.golang.org/grpc v1.27.0
-	gopkg.in/yaml.v2 v2.2.8
+	gopkg.in/yaml.v2 v2.3.0
 	gotest.tools v2.2.0+incompatible
 )
diff --git a/go.sum b/go.sum
index 65cdac5..2c8784d 100644
--- a/go.sum
+++ b/go.sum
@@ -28,7 +28,6 @@
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
 github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -52,6 +51,8 @@
 github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
 github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
@@ -178,6 +179,8 @@
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/bbr/devices/olt.go b/internal/bbr/devices/olt.go
index 01aa411..35b5697 100644
--- a/internal/bbr/devices/olt.go
+++ b/internal/bbr/devices/olt.go
@@ -18,7 +18,7 @@
 
 import (
 	"context"
-	"errors"
+	"encoding/hex"
 	"fmt"
 	"io"
 	"reflect"
@@ -57,7 +57,7 @@
 			if err := onu.InternalState.Event("initialize"); err != nil {
 				log.Fatalf("Error initializing ONU: %v", err)
 			}
-			log.Debugf("Created ONU: %s (%d:%d)", onu.Sn(), onu.STag, onu.CTag)
+			log.Debugf("Created ONU: %s", onu.Sn())
 		}
 	}
 
@@ -91,19 +91,6 @@
 	return client.GetDeviceInfo(ctx, new(openolt.Empty))
 }
 
-func (o *OltMock) getOnuByTags(sTag int, cTag int) (*devices.Onu, error) {
-
-	for _, pon := range o.Olt.Pons {
-		for _, onu := range pon.Onus {
-			if onu.STag == sTag && onu.CTag == cTag {
-				return onu, nil
-			}
-		}
-	}
-
-	return nil, errors.New("cant-find-onu-by-c-s-tags")
-}
-
 func (o *OltMock) readIndications(client openolt.OpenoltClient) {
 	defer func() {
 		log.Info("OLT readIndications done")
@@ -320,28 +307,36 @@
 
 	if pktIndication.IntfType == "nni" {
 		// This is an packet that is arriving from the NNI and needs to be sent to an ONU
-		// in this case we need to fin the ONU from the C/S tags
-		// TODO: handle errors in the untagging process
-		sTag, _ := packetHandlers.GetVlanTag(pkt)
-		singleTagPkt, _ := packetHandlers.PopSingleTag(pkt)
-		cTag, _ := packetHandlers.GetVlanTag(singleTagPkt)
 
-		onu, err := o.getOnuByTags(int(sTag), int(cTag))
+		onuMac, err := packetHandlers.GetDstMacAddressFromPacket(pkt)
 
 		if err != nil {
 			log.WithFields(log.Fields{
-				"sTag": sTag,
-				"cTag": cTag,
-			}).Fatalf("Can't find ONU from c/s tags")
+				"IntfType": "nni",
+				"Pkt":      hex.EncodeToString(pkt.Data()),
+			}).Fatal("Can't find Dst MacAddress in packet")
 		}
 
+		s, err := o.Olt.FindServiceByMacAddress(onuMac)
+		if err != nil {
+			log.WithFields(log.Fields{
+				"IntfType":   "nni",
+				"Pkt":        hex.EncodeToString(pkt.Data()),
+				"MacAddress": onuMac.String(),
+			}).Fatal("Can't find ONU with MacAddress")
+		}
+
+		service := s.(*devices.Service)
+		onu := service.Onu
+
 		msg := devices.Message{
 			Type: devices.OnuPacketIn,
 			Data: devices.OnuPacketMessage{
-				IntfId: pktIndication.IntfId,
-				OnuId:  onu.ID,
-				Packet: pkt,
-				Type:   pktType,
+				IntfId:    pktIndication.IntfId,
+				OnuId:     onu.ID,
+				Packet:    pkt,
+				Type:      pktType,
+				GemPortId: pktIndication.GemportId,
 			},
 		}
 		// NOTE we send it on the ONU channel so that is handled as all the others packets in a separate thread
diff --git a/internal/bbr/devices/validate.go b/internal/bbr/devices/validate.go
index e6d9594..29fcba4 100644
--- a/internal/bbr/devices/validate.go
+++ b/internal/bbr/devices/validate.go
@@ -35,7 +35,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 	defer cancel()
 
-	onus, err := client.GetONUs(ctx, &bbsim.Empty{})
+	services, err := client.GetServices(ctx, &bbsim.Empty{})
 
 	if err != nil {
 		log.WithFields(log.Fields{
@@ -43,25 +43,29 @@
 		}).Fatalf("Can't reach BBSim API")
 	}
 
-	expectedState := "dhcp_ack_received"
+	expectedEapolState := "eap_response_success_received"
+	expectedDhcpState := "dhcp_ack_received"
 
 	res := true
-	for _, onu := range onus.Items {
-		if onu.InternalState != expectedState {
+	for _, service := range services.Items {
+		if service.DhcpState != expectedDhcpState || service.EapolState != expectedEapolState {
 			res = false
 			log.WithFields(log.Fields{
-				"OnuSN":         onu.SerialNumber,
-				"OnuId":         onu.ID,
-				"InternalState": onu.InternalState,
-				"ExpectedSatte": expectedState,
-			}).Error("Not matching expected state")
+				"OnuSN":              service.OnuSn,
+				"ServiceName":        service.Name,
+				"DhcpState":          service.DhcpState,
+				"EapolState":         service.EapolState,
+				"ExpectedDhcpState":  expectedDhcpState,
+				"ExpectedEapolState": expectedEapolState,
+			}).Fatal("Not matching expected state")
 		}
 	}
 
 	if res {
+		// NOTE that in BBR we expect to have a single service but this is not always the case
 		log.WithFields(log.Fields{
-			"ExpectedState": expectedState,
-		}).Infof("%d ONUs matching expected state", len(onus.Items))
+			"ExpectedState": expectedDhcpState,
+		}).Infof("%d ONUs matching expected state", len(services.Items))
 	}
 
 	olt.conn.Close()
diff --git a/internal/bbsim/api/grpc_api_server.go b/internal/bbsim/api/grpc_api_server.go
index 72f999e..7ed8f8d 100644
--- a/internal/bbsim/api/grpc_api_server.go
+++ b/internal/bbsim/api/grpc_api_server.go
@@ -74,7 +74,7 @@
 		pons = append(pons, &p)
 	}
 
-	oltAddress := strings.Split(common.Options.BBSim.OpenOltAddress, ":")[0]
+	oltAddress := strings.Split(common.Config.BBSim.OpenOltAddress, ":")[0]
 	if oltAddress == "" {
 		oltAddress = getOltIP().String()
 	}
diff --git a/internal/bbsim/api/onus_handler.go b/internal/bbsim/api/onus_handler.go
index 3c62f48..fe42b35 100644
--- a/internal/bbsim/api/onus_handler.go
+++ b/internal/bbsim/api/onus_handler.go
@@ -41,10 +41,8 @@
 				OperState:     o.OperState.Current(),
 				InternalState: o.InternalState.Current(),
 				PonPortID:     int32(o.PonPortID),
-				STag:          int32(o.STag),
-				CTag:          int32(o.CTag),
-				HwAddress:     o.HwAddress.String(),
 				PortNo:        int32(o.PortNo),
+				Services:      convertBBsimServicesToProtoServices(o.Services),
 			}
 			onus.Items = append(onus.Items, &onu)
 		}
@@ -67,10 +65,8 @@
 		OperState:     onu.OperState.Current(),
 		InternalState: onu.InternalState.Current(),
 		PonPortID:     int32(onu.PonPortID),
-		STag:          int32(onu.STag),
-		CTag:          int32(onu.CTag),
-		HwAddress:     onu.HwAddress.String(),
 		PortNo:        int32(onu.PortNo),
+		Services:      convertBBsimServicesToProtoServices(onu.Services),
 	}
 	return &res, nil
 }
diff --git a/internal/bbsim/api/services_handler.go b/internal/bbsim/api/services_handler.go
new file mode 100644
index 0000000..d20b527
--- /dev/null
+++ b/internal/bbsim/api/services_handler.go
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package api
+
+import (
+	"context"
+	"github.com/opencord/bbsim/api/bbsim"
+	"github.com/opencord/bbsim/internal/bbsim/devices"
+)
+
+func convertBBSimServiceToProtoService(s *devices.Service) *bbsim.Service {
+	return &bbsim.Service{
+		Name:       s.Name,
+		HwAddress:  s.HwAddress.String(),
+		OnuSn:      s.Onu.Sn(),
+		CTag:       int32(s.CTag),
+		STag:       int32(s.STag),
+		NeedsEapol: s.NeedsEapol,
+		NeedsDhcp:  s.NeedsDhcp,
+		NeedsIgmp:  s.NeedsIgmp,
+		GemPort:    int32(s.GemPort),
+		EapolState: s.EapolState.Current(),
+		DhcpState:  s.DHCPState.Current(),
+	}
+}
+
+func convertBBsimServicesToProtoServices(list []devices.ServiceIf) []*bbsim.Service {
+	services := []*bbsim.Service{}
+	for _, service := range list {
+		s := service.(*devices.Service)
+		services = append(services, convertBBSimServiceToProtoService(s))
+	}
+	return services
+}
+
+func (s BBSimServer) GetServices(ctx context.Context, req *bbsim.Empty) (*bbsim.Services, error) {
+
+	services := bbsim.Services{
+		Items: []*bbsim.Service{},
+	}
+
+	olt := devices.GetOLT()
+
+	for _, pon := range olt.Pons {
+		for _, o := range pon.Onus {
+			s := convertBBsimServicesToProtoServices(o.Services)
+			services.Items = append(services.Items, s...)
+		}
+	}
+
+	return &services, nil
+}
+
+func (s BBSimServer) GetOnuServices(ctx context.Context, req *bbsim.ONURequest) (*bbsim.Services, error) {
+	onu, err := s.GetONU(ctx, req)
+
+	if err != nil {
+		return nil, err
+	}
+
+	services := bbsim.Services{
+		Items: onu.Services,
+	}
+
+	return &services, nil
+}
diff --git a/internal/bbsim/devices/messageTypes.go b/internal/bbsim/devices/messageTypes.go
index f59ea5c..d9ed620 100644
--- a/internal/bbsim/devices/messageTypes.go
+++ b/internal/bbsim/devices/messageTypes.go
@@ -20,6 +20,7 @@
 	"github.com/google/gopacket"
 	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	"github.com/opencord/voltha-protos/v2/go/openolt"
+	"net"
 )
 
 type MessageType int
@@ -33,8 +34,6 @@
 	OMCI              MessageType = 5
 	FlowAdd           MessageType = 6
 	FlowRemoved       MessageType = 18
-	StartEAPOL        MessageType = 7
-	StartDHCP         MessageType = 8
 	OnuPacketOut      MessageType = 9
 
 	// BBR messages
@@ -130,10 +129,12 @@
 }
 
 type OnuPacketMessage struct {
-	IntfId uint32
-	OnuId  uint32
-	Packet gopacket.Packet
-	Type   packetHandlers.PacketType
+	IntfId     uint32
+	OnuId      uint32
+	Packet     gopacket.Packet
+	Type       packetHandlers.PacketType
+	MacAddress net.HardwareAddr
+	GemPortId  uint32 // this is used by BBR
 }
 
 type OperState int
diff --git a/internal/bbsim/devices/nni.go b/internal/bbsim/devices/nni.go
index e74e97b..fe092e8 100644
--- a/internal/bbsim/devices/nni.go
+++ b/internal/bbsim/devices/nni.go
@@ -18,6 +18,7 @@
 
 import (
 	"bytes"
+	"encoding/hex"
 	"os/exec"
 
 	"github.com/google/gopacket"
@@ -110,7 +111,9 @@
 			return err
 		}
 
-		nniLogger.Infof("Sent packet out of NNI")
+		nniLogger.WithFields(log.Fields{
+			"packet": hex.EncodeToString(packet.Data()),
+		}).Trace("Sent packet out of NNI")
 	} else if isLldp {
 		// TODO rework this when BBSim supports data-plane packets
 		nniLogger.Trace("Received LLDP Packet, ignoring it")
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index 21b57f2..eff9c86 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -18,6 +18,7 @@
 
 import (
 	"context"
+	"encoding/hex"
 	"fmt"
 	"net"
 	"sync"
@@ -73,7 +74,7 @@
 	enableContext       context.Context
 	enableContextCancel context.CancelFunc
 
-	OpenoltStream *openolt.Openolt_EnableIndicationServer
+	OpenoltStream openolt.Openolt_EnableIndicationServer
 	enablePerf    bool
 }
 
@@ -84,7 +85,7 @@
 	return &olt
 }
 
-func CreateOLT(options common.BBSimYamlConfig, isMock bool) *OltDevice {
+func CreateOLT(options common.GlobalConfig, services []common.ServiceYaml, isMock bool) *OltDevice {
 	oltLogger.WithFields(log.Fields{
 		"ID":           options.Olt.ID,
 		"NumNni":       options.Olt.NniPorts,
@@ -146,44 +147,61 @@
 		olt.Nnis = append(olt.Nnis, &nniPort)
 	}
 
+	// Create device and Services
+
+	nextCtag := map[string]int{}
+	nextStag := map[string]int{}
+
 	// create PON ports
+	for i := 0; i < olt.NumPon; i++ {
+		p := CreatePonPort(&olt, uint32(i))
 
-	if options.BBSim.STagAllocation == common.TagAllocationShared && options.BBSim.CTagAllocation == common.TagAllocationShared {
-		oltLogger.Fatalf("This configuration will result in duplicate C/S tags combination")
-	} else if options.BBSim.STagAllocation == common.TagAllocationUnique && options.BBSim.CTagAllocation == common.TagAllocationUnique {
-		oltLogger.Fatalf("This configuration is not supported yet")
-	} else if options.BBSim.STagAllocation == common.TagAllocationShared && options.BBSim.CTagAllocation == common.TagAllocationUnique {
-		// ATT case
-		availableCTag := options.BBSim.CTag
-		for i := 0; i < olt.NumPon; i++ {
-			p := CreatePonPort(&olt, uint32(i))
+		// create ONU devices
+		for j := 0; j < olt.NumOnuPerPon; j++ {
+			delay := time.Duration(olt.Delay*j) * time.Millisecond
+			o := CreateONU(&olt, p, uint32(j+1), delay, isMock)
 
-			// create ONU devices
-			for j := 0; j < olt.NumOnuPerPon; j++ {
-				delay := time.Duration(olt.Delay*j) * time.Millisecond
-				o := CreateONU(&olt, p, uint32(j+1), options.BBSim.STag, availableCTag, options.BBSim.EnableAuth, options.BBSim.EnableDhcp, delay, isMock)
-				p.Onus = append(p.Onus, o)
-				availableCTag = availableCTag + 1
+			for k, s := range common.Services {
+
+				// find the correct cTag for this service
+				if _, ok := nextCtag[s.Name]; !ok {
+					// it's the first time we iterate over this service,
+					// so we start from the config value
+					nextCtag[s.Name] = s.CTag
+				} else {
+					// we have a previous value, so we check it
+					// if Allocation is unique, we increment,
+					// otherwise (shared) we do nothing
+					if s.CTagAllocation == common.TagAllocationUnique.String() {
+						nextCtag[s.Name] = nextCtag[s.Name] + 1
+					}
+				}
+
+				// find the correct sTag for this service
+				if _, ok := nextStag[s.Name]; !ok {
+					nextStag[s.Name] = s.STag
+				} else {
+					if s.STagAllocation == common.TagAllocationUnique.String() {
+						nextStag[s.Name] = nextStag[s.Name] + 1
+					}
+				}
+
+				mac := net.HardwareAddr{0x2e, 0x60, byte(olt.ID), byte(p.ID), byte(o.ID), byte(k)}
+				service, err := NewService(s.Name, mac, o, nextCtag[s.Name], nextStag[s.Name],
+					s.NeedsEapol, s.NeedsDchp, s.NeedsIgmp, s.TechnologyProfileID, s.UniTagMatch,
+					s.ConfigureMacAddress, s.UsPonCTagPriority, s.UsPonSTagPriority, s.DsPonCTagPriority, s.DsPonSTagPriority)
+
+				if err != nil {
+					oltLogger.WithFields(log.Fields{
+						"Err": err.Error(),
+					}).Fatal("Can't create Service")
+				}
+
+				o.Services = append(o.Services, service)
 			}
-
-			olt.Pons = append(olt.Pons, p)
+			p.Onus = append(p.Onus, o)
 		}
-	} else if options.BBSim.STagAllocation == common.TagAllocationUnique && options.BBSim.CTagAllocation == common.TagAllocationShared {
-		// DT case
-		availableSTag := options.BBSim.STag
-		for i := 0; i < olt.NumPon; i++ {
-			p := CreatePonPort(&olt, uint32(i))
-
-			// create ONU devices
-			for j := 0; j < olt.NumOnuPerPon; j++ {
-				delay := time.Duration(olt.Delay*j) * time.Millisecond
-				o := CreateONU(&olt, p, uint32(j+1), availableSTag, options.BBSim.CTag, options.BBSim.EnableAuth, options.BBSim.EnableDhcp, delay, isMock)
-				p.Onus = append(p.Onus, o)
-				availableSTag = availableSTag + 1
-			}
-
-			olt.Pons = append(olt.Pons, p)
-		}
+		olt.Pons = append(olt.Pons, p)
 	}
 
 	if !isMock {
@@ -238,7 +256,7 @@
 
 func (o *OltDevice) RestartOLT() error {
 
-	rebootDelay := common.Options.Olt.OltRebootDelay
+	rebootDelay := common.Config.Olt.OltRebootDelay
 
 	oltLogger.WithFields(log.Fields{
 		"oltId": o.ID,
@@ -298,7 +316,7 @@
 
 // newOltServer launches a new grpc server for OpenOLT
 func (o *OltDevice) newOltServer() (*grpc.Server, error) {
-	address := common.Options.BBSim.OpenOltAddress
+	address := common.Config.BBSim.OpenOltAddress
 	lis, err := net.Listen("tcp", address)
 	if err != nil {
 		oltLogger.Fatalf("OLT failed to listen: %v", err)
@@ -351,7 +369,7 @@
 	wg := sync.WaitGroup{}
 	wg.Add(3)
 
-	o.OpenoltStream = &stream
+	o.OpenoltStream = stream
 
 	// create Go routine to process all OLT events
 	go o.processOltMessages(o.enableContext, stream, &wg)
@@ -444,7 +462,7 @@
 				"messageType": message.Type,
 				"OnuId":       message.Data.OnuId,
 				"IntfId":      message.Data.IntfId,
-			}).Info("Received message on OMCI Sim channel")
+			}).Debug("Received message on OMCI Sim channel")
 
 			onuId := message.Data.OnuId
 			intfId := message.Data.IntfId
@@ -577,7 +595,7 @@
 
 func (o *OltDevice) sendPonIndication(ponPortID uint32) {
 
-	stream := *o.OpenoltStream
+	stream := o.OpenoltStream
 	pon, _ := o.GetPonById(ponPortID)
 	// Send IntfIndication for PON port
 	discoverData := &openolt.Indication_IntfInd{IntfInd: &openolt.IntfIndication{
@@ -624,7 +642,7 @@
 		data := &openolt.Indication_PortStats{
 			PortStats: stats,
 		}
-		stream := *o.OpenoltStream
+		stream := o.OpenoltStream
 		if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
 			oltLogger.Errorf("Failed to send PortStats: %v", err)
 			return
@@ -743,7 +761,7 @@
 				return
 			}
 
-			onu, err := o.FindOnuByMacAddress(onuMac)
+			s, err := o.FindServiceByMacAddress(onuMac)
 			if err != nil {
 				log.WithFields(log.Fields{
 					"IntfType":   "nni",
@@ -754,7 +772,9 @@
 				return
 			}
 
-			doubleTaggedPkt, err := packetHandlers.PushDoubleTag(onu.STag, onu.CTag, message.Pkt)
+			service := s.(*Service)
+
+			doubleTaggedPkt, err := packetHandlers.PushDoubleTag(service.STag, service.CTag, message.Pkt)
 			if err != nil {
 				log.Error("Fail to add double tag to packet")
 			}
@@ -773,9 +793,9 @@
 			oltLogger.WithFields(log.Fields{
 				"IntfType": data.PktInd.IntfType,
 				"IntfId":   nniId,
-				"Pkt":      doubleTaggedPkt.Data(),
-				"OnuSn":    onu.Sn(),
-			}).Tracef("Sent PktInd indication")
+				"Pkt":      hex.EncodeToString(doubleTaggedPkt.Data()),
+				"OnuSn":    service.Onu.Sn(),
+			}).Trace("Sent PktInd indication (from NNI to VOLTHA)")
 		}
 	}
 	wg.Done()
@@ -815,19 +835,20 @@
 	return &Onu{}, fmt.Errorf("cannot-find-onu-by-id-%v-%v", intfId, onuId)
 }
 
-// returns an ONU with a given Mac Address
-func (o *OltDevice) FindOnuByMacAddress(mac net.HardwareAddr) (*Onu, error) {
+// returns a Service with a given Mac Address
+func (o *OltDevice) FindServiceByMacAddress(mac net.HardwareAddr) (ServiceIf, error) {
 	// TODO this function can be a performance bottleneck when we have many ONUs,
 	// memoizing it will remove the bottleneck
 	for _, pon := range o.Pons {
 		for _, onu := range pon.Onus {
-			if onu.HwAddress.String() == mac.String() {
-				return onu, nil
+			s, err := onu.findServiceByMacAddress(mac)
+			if err == nil {
+				return s, nil
 			}
 		}
 	}
 
-	return &Onu{}, fmt.Errorf("cannot-find-onu-by-mac-address-%s", mac)
+	return nil, fmt.Errorf("cannot-find-service-by-mac-address-%s", mac)
 }
 
 // GRPC Endpoints
@@ -963,7 +984,7 @@
 			OnuID:     onu.ID,
 			OnuSN:     onu.SerialNumber,
 		}
-		onu.sendOnuIndication(onuIndication, *o.OpenoltStream)
+		onu.sendOnuIndication(onuIndication, o.OpenoltStream)
 
 	}
 
@@ -999,7 +1020,7 @@
 			OnuID:     onu.ID,
 			OnuSN:     onu.SerialNumber,
 		}
-		onu.sendOnuIndication(onuIndication, *o.OpenoltStream)
+		onu.sendOnuIndication(onuIndication, o.OpenoltStream)
 
 	}
 
@@ -1174,11 +1195,11 @@
 		"PonPorts": o.NumPon,
 	}).Info("OLT receives GetDeviceInfo call from VOLTHA")
 	devinfo := new(openolt.DeviceInfo)
-	devinfo.Vendor = common.Options.Olt.Vendor
-	devinfo.Model = common.Options.Olt.Model
-	devinfo.HardwareVersion = common.Options.Olt.HardwareVersion
-	devinfo.FirmwareVersion = common.Options.Olt.FirmwareVersion
-	devinfo.Technology = common.Options.Olt.Technology
+	devinfo.Vendor = common.Config.Olt.Vendor
+	devinfo.Model = common.Config.Olt.Model
+	devinfo.HardwareVersion = common.Config.Olt.HardwareVersion
+	devinfo.FirmwareVersion = common.Config.Olt.FirmwareVersion
+	devinfo.Technology = common.Config.Olt.Technology
 	devinfo.PonPorts = uint32(o.NumPon)
 	devinfo.OnuIdStart = 1
 	devinfo.OnuIdEnd = 255
@@ -1189,7 +1210,7 @@
 	devinfo.FlowIdStart = 1
 	devinfo.FlowIdEnd = 16383
 	devinfo.DeviceSerialNumber = o.SerialNumber
-	devinfo.DeviceId = common.Options.Olt.DeviceId
+	devinfo.DeviceId = common.Config.Olt.DeviceId
 
 	return devinfo, nil
 }
@@ -1254,20 +1275,34 @@
 		"IntfId": onu.PonPortID,
 		"OnuId":  onu.ID,
 		"OnuSn":  onu.Sn(),
-	}).Tracef("Received OnuPacketOut")
+	}).Info("Received OnuPacketOut")
 
 	rawpkt := gopacket.NewPacket(onuPkt.Pkt, layers.LayerTypeEthernet, gopacket.Default)
 	pktType, _ := packetHandlers.IsEapolOrDhcp(rawpkt)
 
+	pktMac, err := packetHandlers.GetDstMacAddressFromPacket(rawpkt)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"IntfId": onu.PonPortID,
+			"OnuId":  onu.ID,
+			"OnuSn":  onu.Sn(),
+			"Pkt":    rawpkt.Data(),
+		}).Error("Can't find Dst MacAddress in packet, droppint it")
+		return new(openolt.Empty), nil
+	}
+
 	msg := Message{
 		Type: OnuPacketOut,
 		Data: OnuPacketMessage{
-			IntfId: onuPkt.IntfId,
-			OnuId:  onuPkt.OnuId,
-			Packet: rawpkt,
-			Type:   pktType,
+			IntfId:     onuPkt.IntfId,
+			OnuId:      onuPkt.OnuId,
+			Packet:     rawpkt,
+			Type:       pktType,
+			MacAddress: pktMac,
 		},
 	}
+
 	onu.Channel <- msg
 
 	return new(openolt.Empty), nil
diff --git a/internal/bbsim/devices/olt_test.go b/internal/bbsim/devices/olt_test.go
index 8aa0ae6..53f9f44 100644
--- a/internal/bbsim/devices/olt_test.go
+++ b/internal/bbsim/devices/olt_test.go
@@ -17,6 +17,7 @@
 package devices
 
 import (
+	"github.com/opencord/bbsim/internal/common"
 	"net"
 	"testing"
 
@@ -24,7 +25,7 @@
 	"gotest.tools/assert"
 )
 
-func createMockOlt(numPon int, numOnu int) *OltDevice {
+func createMockOlt(numPon int, numOnu int, services []ServiceIf) *OltDevice {
 	olt := &OltDevice{
 		ID: 0,
 	}
@@ -40,8 +41,15 @@
 				ID:        onuId,
 				PonPort:   &pon,
 				PonPortID: pon.ID,
-				HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(onuId)},
 			}
+
+			for k, s := range services {
+				service := s.(*Service)
+				service.HwAddress = net.HardwareAddr{0x2e, 0x60, byte(olt.ID), byte(pon.ID), byte(onuId), byte(k)}
+				service.Onu = &onu
+				onu.Services = append(onu.Services, service)
+			}
+
 			onu.SerialNumber = onu.NewSN(olt.ID, pon.ID, onu.ID)
 			pon.Onus = append(pon.Onus, &onu)
 		}
@@ -50,12 +58,76 @@
 	return olt
 }
 
+// check the creation of an OLT with a single Service
+func TestCreateOLT(t *testing.T) {
+
+	common.Services = []common.ServiceYaml{
+		{Name: "hsia", CTag: 900, CTagAllocation: common.TagAllocationUnique.String(), STag: 900, STagAllocation: common.TagAllocationShared.String(), NeedsEapol: true, NeedsDchp: true, NeedsIgmp: true},
+	}
+
+	common.Config = &common.GlobalConfig{
+		Olt: common.OltConfig{
+			ID:          1,
+			PonPorts:    2,
+			OnusPonPort: 2,
+		},
+	}
+
+	olt := CreateOLT(*common.Config, common.Services, true)
+
+	assert.Equal(t, len(olt.Pons), int(common.Config.Olt.PonPorts))
+
+	// count the ONUs
+	onus := 0
+	for _, p := range olt.Pons {
+		onus = onus + len(p.Onus)
+	}
+
+	assert.Equal(t, onus, int(common.Config.Olt.PonPorts*common.Config.Olt.OnusPonPort))
+
+	// count the services
+	services := 0
+	for _, p := range olt.Pons {
+		for _, o := range p.Onus {
+			services = services + len(o.Services)
+		}
+	}
+
+	assert.Equal(t, services, int(common.Config.Olt.PonPorts)*int(common.Config.Olt.OnusPonPort)*len(common.Services))
+
+	s1 := olt.Pons[0].Onus[0].Services[0].(*Service)
+
+	assert.Equal(t, s1.Name, "hsia")
+	assert.Equal(t, s1.CTag, 900)
+	assert.Equal(t, s1.STag, 900)
+	assert.Equal(t, s1.HwAddress.String(), "2e:60:01:00:01:00")
+	assert.Equal(t, olt.Pons[0].Onus[0].ID, uint32(1))
+
+	s2 := olt.Pons[0].Onus[1].Services[0].(*Service)
+	assert.Equal(t, s2.CTag, 901)
+	assert.Equal(t, s2.STag, 900)
+	assert.Equal(t, s2.HwAddress.String(), "2e:60:01:00:02:00")
+	assert.Equal(t, olt.Pons[0].Onus[1].ID, uint32(2))
+
+	s3 := olt.Pons[1].Onus[0].Services[0].(*Service)
+	assert.Equal(t, s3.CTag, 902)
+	assert.Equal(t, s3.STag, 900)
+	assert.Equal(t, s3.HwAddress.String(), "2e:60:01:01:01:00")
+	assert.Equal(t, olt.Pons[1].Onus[0].ID, uint32(1))
+
+	s4 := olt.Pons[1].Onus[1].Services[0].(*Service)
+	assert.Equal(t, s4.CTag, 903)
+	assert.Equal(t, s4.STag, 900)
+	assert.Equal(t, s4.HwAddress.String(), "2e:60:01:01:02:00")
+	assert.Equal(t, olt.Pons[1].Onus[1].ID, uint32(2))
+}
+
 func Test_Olt_FindOnuBySn_Success(t *testing.T) {
 
 	numPon := 4
 	numOnu := 4
 
-	olt := createMockOlt(numPon, numOnu)
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
 
 	onu, err := olt.FindOnuBySn("BBSM00000303")
 
@@ -70,7 +142,7 @@
 	numPon := 1
 	numOnu := 4
 
-	olt := createMockOlt(numPon, numOnu)
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
 
 	_, err := olt.FindOnuBySn("BBSM00000303")
 
@@ -78,20 +150,30 @@
 }
 
 func Test_Olt_FindOnuByMacAddress_Success(t *testing.T) {
-
 	numPon := 4
 	numOnu := 4
 
-	olt := createMockOlt(numPon, numOnu)
+	services := []ServiceIf{
+		&Service{Name: "hsia"},
+		&Service{Name: "voip"},
+		&Service{Name: "vod"},
+	}
 
-	mac := net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(3), byte(3)}
+	olt := createMockOlt(numPon, numOnu, services)
 
-	onu, err := olt.FindOnuByMacAddress(mac)
+	mac := net.HardwareAddr{0x2e, 0x60, byte(olt.ID), byte(3), byte(6), byte(1)}
+	s, err := olt.FindServiceByMacAddress(mac)
+
+	assert.NilError(t, err)
+
+	service := s.(*Service)
 
 	assert.Equal(t, err, nil)
-	assert.Equal(t, onu.Sn(), "BBSM00000303")
-	assert.Equal(t, onu.ID, uint32(3))
-	assert.Equal(t, onu.PonPortID, uint32(3))
+	assert.Equal(t, service.Onu.Sn(), "BBSM00000306")
+	assert.Equal(t, service.Onu.ID, uint32(6))
+	assert.Equal(t, service.Onu.PonPortID, uint32(3))
+
+	assert.Equal(t, service.Name, "voip")
 }
 
 func Test_Olt_FindOnuByMacAddress_Error(t *testing.T) {
@@ -99,20 +181,20 @@
 	numPon := 1
 	numOnu := 4
 
-	olt := createMockOlt(numPon, numOnu)
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
 
 	mac := net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(3), byte(3)}
 
-	_, err := olt.FindOnuByMacAddress(mac)
+	_, err := olt.FindServiceByMacAddress(mac)
 
-	assert.Equal(t, err.Error(), "cannot-find-onu-by-mac-address-2e:60:70:13:03:03")
+	assert.Equal(t, err.Error(), "cannot-find-service-by-mac-address-2e:60:70:13:03:03")
 }
 
 func Test_Olt_GetOnuByFlowId(t *testing.T) {
 	numPon := 4
 	numOnu := 4
 
-	olt := createMockOlt(numPon, numOnu)
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
 
 	// Add the flows to onus (to be found)
 	onu1, _ := olt.FindOnuBySn("BBSM00000303")
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index 6c53b68..0f78107 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -18,8 +18,10 @@
 
 import (
 	"context"
-	"errors"
 	"fmt"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
+	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
+	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
 	"net"
 
 	"time"
@@ -28,10 +30,6 @@
 	"github.com/google/gopacket/layers"
 	"github.com/jpillora/backoff"
 	"github.com/looplab/fsm"
-	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
-	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
-	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
-	"github.com/opencord/bbsim/internal/bbsim/responders/igmp"
 	"github.com/opencord/bbsim/internal/common"
 	omcilib "github.com/opencord/bbsim/internal/common/omci"
 	omcisim "github.com/opencord/omci-sim"
@@ -53,31 +51,26 @@
 	ID                  uint32
 	PonPortID           uint32
 	PonPort             *PonPort
-	STag                int
-	CTag                int
-	Auth                bool // automatically start EAPOL if set to true
-	Dhcp                bool // automatically start DHCP if set to true
-	HwAddress           net.HardwareAddr
 	InternalState       *fsm.FSM
 	DiscoveryRetryDelay time.Duration // this is the time between subsequent Discovery Indication
 	DiscoveryDelay      time.Duration // this is the time to send the first Discovery Indication
-	Backoff             *backoff.Backoff
+
+	Services []ServiceIf
+
+	Backoff *backoff.Backoff
 	// ONU State
 	// PortNo comes with flows and it's used when sending packetIndications,
 	// There is one PortNo per UNI Port, for now we're only storing the first one
 	// FIXME add support for multiple UNIs (each UNI has a different PortNo)
-	PortNo            uint32
-	GemPortAdded      bool
-	EapolFlowReceived bool
-	DhcpFlowReceived  bool
-	Flows             []FlowKey
-	FlowIds           []uint32 // keep track of the flows we currently have in the ONU
+	PortNo       uint32
+	GemPortAdded bool
+	Flows        []FlowKey
+	FlowIds      []uint32 // keep track of the flows we currently have in the ONU
 
 	OperState    *fsm.FSM
 	SerialNumber *openolt.SerialNumber
 
-	Channel         chan Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
-	GemPortChannels []chan bool  // this channels are used to notify everyone that is interested that a GemPort has been added
+	Channel chan Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
 
 	// OMCI params
 	tid       uint16
@@ -92,13 +85,7 @@
 	return common.OnuSnToString(o.SerialNumber)
 }
 
-func (o *Onu) GetGemPortChan() chan bool {
-	listener := make(chan bool, 1)
-	o.GemPortChannels = append(o.GemPortChannels, listener)
-	return listener
-}
-
-func CreateONU(olt *OltDevice, pon *PonPort, id uint32, sTag int, cTag int, auth bool, dhcp bool, delay time.Duration, isMock bool) *Onu {
+func CreateONU(olt *OltDevice, pon *PonPort, id uint32, delay time.Duration, isMock bool) *Onu {
 	b := &backoff.Backoff{
 		//These are the defaults
 		Min:    5 * time.Second,
@@ -108,21 +95,14 @@
 	}
 
 	o := Onu{
-		ID:                  0,
+		ID:                  id,
 		PonPortID:           pon.ID,
 		PonPort:             pon,
-		STag:                sTag,
-		CTag:                cTag,
-		Auth:                auth,
-		Dhcp:                dhcp,
-		HwAddress:           net.HardwareAddr{0x2e, 0x60, 0x70, byte(olt.ID), byte(pon.ID), byte(id)},
 		PortNo:              0,
 		tid:                 0x1,
 		hpTid:               0x8000,
 		seqNumber:           0,
 		DoneChannel:         make(chan bool, 1),
-		DhcpFlowReceived:    false,
-		EapolFlowReceived:   false,
 		GemPortAdded:        false,
 		DiscoveryRetryDelay: 60 * time.Second, // this is used to send OnuDiscoveryIndications until an activate call is received
 		Flows:               []FlowKey{},
@@ -152,28 +132,10 @@
 			{Name: "disable", Src: []string{"enabled", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed", "pon_disabled"}, Dst: "disabled"},
 			// ONU state when PON port is disabled but ONU is power ON(more states should be added in src?)
 			{Name: "pon_disabled", Src: []string{"enabled", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed"}, Dst: "pon_disabled"},
-			// EAPOL
-			{Name: "start_auth", Src: []string{"enabled", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent", "eap_response_success_received", "auth_failed", "dhcp_ack_received", "dhcp_failed", "igmp_join_started", "igmp_left", "igmp_join_error"}, Dst: "auth_started"},
-			{Name: "eap_start_sent", Src: []string{"auth_started"}, Dst: "eap_start_sent"},
-			{Name: "eap_response_identity_sent", Src: []string{"eap_start_sent"}, Dst: "eap_response_identity_sent"},
-			{Name: "eap_response_challenge_sent", Src: []string{"eap_response_identity_sent"}, Dst: "eap_response_challenge_sent"},
-			{Name: "eap_response_success_received", Src: []string{"eap_response_challenge_sent"}, Dst: "eap_response_success_received"},
-			{Name: "auth_failed", Src: []string{"auth_started", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent"}, Dst: "auth_failed"},
-			// DHCP
-			{Name: "start_dhcp", Src: []string{"enabled", "eap_response_success_received", "dhcp_discovery_sent", "dhcp_request_sent", "dhcp_ack_received", "dhcp_failed", "igmp_join_started", "igmp_left", "igmp_join_error"}, Dst: "dhcp_started"},
-			{Name: "dhcp_discovery_sent", Src: []string{"dhcp_started"}, Dst: "dhcp_discovery_sent"},
-			{Name: "dhcp_request_sent", Src: []string{"dhcp_discovery_sent"}, Dst: "dhcp_request_sent"},
-			{Name: "dhcp_ack_received", Src: []string{"dhcp_request_sent"}, Dst: "dhcp_ack_received"},
-			{Name: "dhcp_failed", Src: []string{"dhcp_started", "dhcp_discovery_sent", "dhcp_request_sent"}, Dst: "dhcp_failed"},
 			// BBR States
 			// TODO add start OMCI state
 			{Name: "send_eapol_flow", Src: []string{"initialized"}, Dst: "eapol_flow_sent"},
 			{Name: "send_dhcp_flow", Src: []string{"eapol_flow_sent"}, Dst: "dhcp_flow_sent"},
-			// IGMP
-			{Name: "igmp_join_start", Src: []string{"eap_response_success_received", "dhcp_ack_received", "igmp_left", "igmp_join_error", "igmp_join_started"}, Dst: "igmp_join_started"},
-			{Name: "igmp_join_startv3", Src: []string{"eap_response_success_received", "dhcp_ack_received", "igmp_left", "igmp_join_error", "igmp_join_started"}, Dst: "igmp_join_started"},
-			{Name: "igmp_join_error", Src: []string{"igmp_join_started"}, Dst: "igmp_join_error"},
-			{Name: "igmp_leave", Src: []string{"igmp_join_started", "eap_response_success_received", "dhcp_ack_received"}, Dst: "igmp_left"},
 		},
 		fsm.Callbacks{
 			"enter_state": func(e *fsm.Event) {
@@ -193,7 +155,7 @@
 
 				if !isMock {
 					// start ProcessOnuMessages Go routine
-					go o.ProcessOnuMessages(olt.enableContext, *olt.OpenoltStream, nil)
+					go o.ProcessOnuMessages(olt.enableContext, olt.OpenoltStream, nil)
 				}
 			},
 			"enter_discovered": func(e *fsm.Event) {
@@ -220,8 +182,6 @@
 			"enter_disabled": func(event *fsm.Event) {
 
 				// clean the ONU state
-				o.DhcpFlowReceived = false
-				o.EapolFlowReceived = false
 				o.GemPortAdded = false
 				o.PortNo = 0
 				o.Flows = []FlowKey{}
@@ -252,75 +212,7 @@
 					close(o.Channel)
 				}
 			},
-			"before_start_auth": func(e *fsm.Event) {
-				if !o.EapolFlowReceived {
-					e.Cancel(errors.New("cannot-go-to-auth-started-as-eapol-flow-is-missing"))
-					return
-				}
-				if !o.GemPortAdded {
-					e.Cancel(errors.New("cannot-go-to-auth-started-as-gemport-is-missing"))
-					return
-				}
-			},
-			"enter_auth_started": func(e *fsm.Event) {
-				o.logStateChange(e.Src, e.Dst)
-				msg := Message{
-					Type: StartEAPOL,
-					Data: PacketMessage{
-						PonPortID: o.PonPortID,
-						OnuID:     o.ID,
-					},
-				}
-				o.Channel <- msg
-			},
-			"enter_eap_response_success_received": func(e *fsm.Event) {
-				publishEvent("ONU-authentication-done", int32(o.PonPortID), int32(o.ID), o.Sn())
-			},
-			"enter_auth_failed": func(e *fsm.Event) {
-				onuLogger.WithFields(log.Fields{
-					"OnuId":  o.ID,
-					"IntfId": o.PonPortID,
-					"OnuSn":  o.Sn(),
-				}).Errorf("ONU failed to authenticate!")
-			},
-			"before_start_dhcp": func(e *fsm.Event) {
-
-				// we allow transition from eanbled to dhcp_started only if auth was set to false
-				if o.InternalState.Current() == "enabled" && o.Auth {
-					e.Cancel(errors.New("cannot-go-to-dhcp-started-as-authentication-is-required"))
-					return
-				}
-
-				if !o.DhcpFlowReceived {
-					e.Cancel(errors.New("cannot-go-to-dhcp-started-as-dhcp-flow-is-missing"))
-					return
-				}
-
-				if !o.GemPortAdded {
-					e.Cancel(errors.New("cannot-go-to-dhcp-started-as-gemport-is-missing"))
-					return
-				}
-			},
-			"enter_dhcp_started": func(e *fsm.Event) {
-				msg := Message{
-					Type: StartDHCP,
-					Data: PacketMessage{
-						PonPortID: o.PonPortID,
-						OnuID:     o.ID,
-					},
-				}
-				o.Channel <- msg
-			},
-			"enter_dhcp_ack_received": func(e *fsm.Event) {
-				publishEvent("ONU-DHCP-ACK-received", int32(o.PonPortID), int32(o.ID), o.Sn())
-			},
-			"enter_dhcp_failed": func(e *fsm.Event) {
-				onuLogger.WithFields(log.Fields{
-					"OnuId":  o.ID,
-					"IntfId": o.PonPortID,
-					"OnuSn":  o.Sn(),
-				}).Errorf("ONU failed to DHCP!")
-			},
+			// BBR states
 			"enter_eapol_flow_sent": func(e *fsm.Event) {
 				msg := Message{
 					Type: SendEapolFlow,
@@ -413,10 +305,6 @@
 			case FlowRemoved:
 				msg, _ := message.Data.(OnuFlowUpdateMessage)
 				o.handleFlowRemove(msg)
-			case StartEAPOL:
-				o.handleEAPOLStart(stream)
-			case StartDHCP:
-				o.handleDHCPStart(stream)
 			case OnuPacketOut:
 
 				msg, _ := message.Data.(OnuPacketMessage)
@@ -427,13 +315,20 @@
 					"pktType": msg.Type,
 				}).Trace("Received OnuPacketOut Message")
 
-				if msg.Type == packetHandlers.EAPOL {
-					eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
-				} else if msg.Type == packetHandlers.DHCP {
-					// NOTE here we receive packets going from the DHCP Server to the ONU
-					// for now we expect them to be double-tagged, but ideally the should be single tagged
-					_ = dhcp.HandleNextPacket(o.PonPort.Olt.ID, o.ID, o.PonPortID, o.Sn(), o.PortNo, o.HwAddress, o.CTag, o.InternalState, msg.Packet, stream)
+				service, err := o.findServiceByMacAddress(msg.MacAddress)
+				if err != nil {
+					onuLogger.WithFields(log.Fields{
+						"IntfId":     msg.IntfId,
+						"OnuId":      msg.OnuId,
+						"pktType":    msg.Type,
+						"MacAddress": msg.MacAddress,
+						"OnuSn":      o.Sn(),
+					}).Error("Cannot find Service associated with packet")
+					return
 				}
+
+				service.PacketCh <- msg
+
 			case OnuPacketIn:
 				// NOTE we only receive BBR packets here.
 				// Eapol.HandleNextPacket can handle both BBSim and BBr cases so the call is the same
@@ -447,9 +342,9 @@
 				}).Trace("Received OnuPacketIn Message")
 
 				if msg.Type == packetHandlers.EAPOL {
-					eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
+					eapol.HandleNextPacket(msg.OnuId, msg.IntfId, msg.GemPortId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
 				} else if msg.Type == packetHandlers.DHCP {
-					_ = dhcp.HandleNextBbrPacket(o.ID, o.PonPortID, o.Sn(), o.STag, o.HwAddress, o.DoneChannel, msg.Packet, client)
+					_ = dhcp.HandleNextBbrPacket(o.ID, o.PonPortID, o.Sn(), o.DoneChannel, msg.Packet, client)
 				}
 			case OmciIndication:
 				msg, _ := message.Data.(OmciIndicationMessage)
@@ -458,15 +353,6 @@
 				o.sendEapolFlow(client)
 			case SendDhcpFlow:
 				o.sendDhcpFlow(client)
-			case IGMPMembershipReportV2:
-				log.Infof("Recieved IGMPMembershipReportV2 message on ONU channel")
-				_ = igmp.SendIGMPMembershipReportV2(o.PonPortID, o.ID, o.Sn(), o.PortNo, o.HwAddress, stream)
-			case IGMPLeaveGroup:
-				log.Infof("Recieved IGMPLeaveGroupV2 message on ONU channel")
-				_ = igmp.SendIGMPLeaveGroupV2(o.PonPortID, o.ID, o.Sn(), o.PortNo, o.HwAddress, stream)
-			case IGMPMembershipReportV3:
-				log.Infof("Recieved IGMPMembershipReportV3 message on ONU channel")
-				_ = igmp.SendIGMPMembershipReportV3(o.PonPortID, o.ID, o.Sn(), o.PortNo, o.HwAddress, stream)
 			default:
 				onuLogger.Warnf("Received unknown message data %v for type %v in OLT Channel", message.Data, message.Type)
 			}
@@ -485,7 +371,7 @@
 			"OnuId":  message.Data.OnuId,
 			"IntfId": message.Data.IntfId,
 			"Type":   message.Type,
-		}).Infof("UNI Link Alarm")
+		}).Debug("UNI Link Alarm")
 		// TODO send to OLT
 
 		omciInd := openolt.OmciIndication{
@@ -510,60 +396,10 @@
 			"SerialNumber": o.Sn(),
 			"Type":         message.Type,
 			"omciPacket":   omciInd.Pkt,
-		}).Info("UNI Link alarm sent")
-
-	case omcisim.GemPortAdded:
-		log.WithFields(log.Fields{
-			"OnuId":  message.Data.OnuId,
-			"IntfId": message.Data.IntfId,
-			"OnuSn":  o.Sn(),
-		}).Infof("GemPort Added")
-
-		o.GemPortAdded = true
-
-		// broadcast the change to all listeners
-		// and close the channels as once the GemPort is set
-		// it won't change anymore
-		for _, ch := range o.GemPortChannels {
-			ch <- true
-			close(ch)
-		}
-		o.GemPortChannels = []chan bool{}
+		}).Debug("UNI Link alarm sent")
 	}
 }
 
-func (o *Onu) handleEAPOLStart(stream openolt.Openolt_EnableIndicationServer) {
-	log.Infof("Receive StartEAPOL message on ONU Channel")
-	_ = eapol.SendEapStart(o.ID, o.PonPortID, o.Sn(), o.PortNo, o.HwAddress, o.InternalState, stream)
-	go func(delay time.Duration) {
-		time.Sleep(delay)
-		if (o.InternalState.Current() == "eap_start_sent" ||
-			o.InternalState.Current() == "eap_response_identity_sent" ||
-			o.InternalState.Current() == "eap_response_challenge_sent" ||
-			o.InternalState.Current() == "auth_failed") && common.Options.BBSim.AuthRetry {
-			_ = o.InternalState.Event("start_auth")
-		} else if o.InternalState.Current() == "eap_response_success_received" {
-			o.Backoff.Reset()
-		}
-	}(o.Backoff.Duration())
-}
-
-func (o *Onu) handleDHCPStart(stream openolt.Openolt_EnableIndicationServer) {
-	log.Infof("Receive StartDHCP message on ONU Channel")
-	// FIXME use id, ponId as SendEapStart
-	_ = dhcp.SendDHCPDiscovery(o.PonPort.Olt.ID, o.PonPortID, o.ID, o.Sn(), o.PortNo, o.InternalState, o.HwAddress, o.CTag, stream)
-	go func(delay time.Duration) {
-		time.Sleep(delay)
-		if (o.InternalState.Current() == "dhcp_discovery_sent" ||
-			o.InternalState.Current() == "dhcp_request_sent" ||
-			o.InternalState.Current() == "dhcp_failed") && common.Options.BBSim.DhcpRetry {
-			_ = o.InternalState.Event("start_dhcp")
-		} else if o.InternalState.Current() == "dhcp_ack_received" {
-			o.Backoff.Reset()
-		}
-	}(o.Backoff.Duration())
-}
-
 func (o Onu) NewSN(oltid int, intfid uint32, onuid uint32) *openolt.SerialNumber {
 
 	sn := new(openolt.SerialNumber)
@@ -627,6 +463,10 @@
 		"OnuSn":      o.Sn(),
 	}).Debug("Sent Indication_OnuInd")
 
+	for _, s := range o.Services {
+		go s.HandlePackets(stream)
+	}
+
 }
 
 func (o *Onu) publishOmciEvent(msg OmciMessage) {
@@ -790,83 +630,22 @@
 	}
 
 	o.FlowIds = append(o.FlowIds, msg.Flow.FlowId)
+	o.addGemPortToService(uint32(msg.Flow.GemportId), msg.Flow.Classifier.EthType, msg.Flow.Classifier.OVid, msg.Flow.Classifier.IVid)
 
 	if msg.Flow.Classifier.EthType == uint32(layers.EthernetTypeEAPOL) && msg.Flow.Classifier.OVid == 4091 {
 		// NOTE storing the PortNO, it's needed when sending PacketIndications
 		o.storePortNumber(uint32(msg.Flow.PortNo))
-		o.EapolFlowReceived = true
-		// if authentication is not enabled, do nothing
-		if o.Auth {
-			// NOTE if we receive the EAPOL flows but we don't have GemPorts
-			// wait for it before starting auth
-			if !o.GemPortAdded {
-				// wait for Gem and then start auth
-				go func() {
-					for v := range o.GetGemPortChan() {
-						if v {
-							if err := o.InternalState.Event("start_auth"); err != nil {
-								onuLogger.Warnf("Can't go to auth_started: %v", err)
-							}
-						}
-					}
-					onuLogger.Trace("GemPortChannel closed")
-				}()
-			} else {
-				// start the EAPOL state machine
-				if err := o.InternalState.Event("start_auth"); err != nil {
-					onuLogger.Warnf("Can't go to auth_started: %v", err)
-				}
-			}
-		} else {
-			onuLogger.WithFields(log.Fields{
-				"IntfId":       o.PonPortID,
-				"OnuId":        o.ID,
-				"SerialNumber": o.Sn(),
-			}).Warn("Not starting authentication as Auth bit is not set in CLI parameters")
+
+		for _, s := range o.Services {
+			s.HandleAuth(o.PonPort.Olt.OpenoltStream)
 		}
 	} else if msg.Flow.Classifier.EthType == uint32(layers.EthernetTypeIPv4) &&
 		msg.Flow.Classifier.SrcPort == uint32(68) &&
 		msg.Flow.Classifier.DstPort == uint32(67) &&
 		(msg.Flow.Classifier.OPbits == 0 || msg.Flow.Classifier.OPbits == 255) {
 
-		if o.Dhcp {
-			if !o.DhcpFlowReceived {
-				// keep track that we received the DHCP Flows
-				// so that we can transition the state to dhcp_started
-				// this is needed as a check in case someone trigger DHCP from the CLI
-				o.DhcpFlowReceived = true
-
-				if !o.GemPortAdded {
-					// wait for Gem and then start DHCP
-					go func() {
-						for v := range o.GetGemPortChan() {
-							if v {
-								if err := o.InternalState.Event("start_dhcp"); err != nil {
-									log.Errorf("Can't go to dhcp_started: %v", err)
-								}
-							}
-						}
-					}()
-				} else {
-					// start the DHCP state machine
-					if err := o.InternalState.Event("start_dhcp"); err != nil {
-						log.Errorf("Can't go to dhcp_started: %v", err)
-					}
-				}
-			} else {
-				onuLogger.WithFields(log.Fields{
-					"IntfId":           o.PonPortID,
-					"OnuId":            o.ID,
-					"SerialNumber":     o.Sn(),
-					"DhcpFlowReceived": o.DhcpFlowReceived,
-				}).Warn("DHCP already started")
-			}
-		} else {
-			onuLogger.WithFields(log.Fields{
-				"IntfId":       o.PonPortID,
-				"OnuId":        o.ID,
-				"SerialNumber": o.Sn(),
-			}).Warn("Not starting DHCP as Dhcp bit is not set in CLI parameters")
+		for _, s := range o.Services {
+			s.HandleDhcp(o.PonPort.Olt.OpenoltStream, int(msg.Flow.Classifier.OVid))
 		}
 	}
 }
@@ -896,11 +675,6 @@
 		}).Info("Resetting GemPort")
 		o.GemPortAdded = false
 
-		// TODO ideally we should keep track of the flow type (and not only the ID)
-		// so that we can properly set these two flag when the flow is removed
-		o.EapolFlowReceived = false
-		o.DhcpFlowReceived = false
-
 		// check if ONU delete is performed and
 		// terminate the ONU's ProcessOnuMessages Go routine
 		if o.InternalState.Current() == "disabled" {
@@ -1075,10 +849,15 @@
 }
 
 func (o *Onu) sendDhcpFlow(client openolt.OpenoltClient) {
+
+	// BBR only works with a single service (ATT HSIA)
+	hsia := o.Services[0].(*Service)
+
 	classifierProto := openolt.Classifier{
 		EthType: uint32(layers.EthernetTypeIPv4),
 		SrcPort: uint32(68),
 		DstPort: uint32(67),
+		OVid:    uint32(hsia.CTag),
 	}
 
 	actionProto := openolt.Action{}
@@ -1158,3 +937,36 @@
 		}).Infof("Failed to transition ONU to discovered state: %s", err.Error())
 	}
 }
+
+func (onu *Onu) addGemPortToService(gemport uint32, ethType uint32, oVlan uint32, iVlan uint32) {
+	for _, s := range onu.Services {
+		if service, ok := s.(*Service); ok {
+			// EAPOL is a strange case, as packets are untagged
+			// but we assume we will have a single service requiring EAPOL
+			if ethType == uint32(layers.EthernetTypeEAPOL) && service.NeedsEapol {
+				service.GemPort = gemport
+			}
+
+			// For DHCP services we single tag the outgoing packets,
+			// thus the flow only contains the CTag and we can use that to match the service
+			if ethType == uint32(layers.EthernetTypeIPv4) && service.NeedsDhcp && service.CTag == int(oVlan) {
+				service.GemPort = gemport
+			}
+
+			// for dataplane services match both C and S tags
+			if service.CTag == int(iVlan) && service.STag == int(oVlan) {
+				service.GemPort = gemport
+			}
+		}
+	}
+}
+
+func (onu *Onu) findServiceByMacAddress(macAddress net.HardwareAddr) (*Service, error) {
+	for _, s := range onu.Services {
+		service := s.(*Service)
+		if service.HwAddress.String() == macAddress.String() {
+			return service, nil
+		}
+	}
+	return nil, fmt.Errorf("cannot-find-service-with-mac-address-%s", macAddress.String())
+}
diff --git a/internal/bbsim/devices/onu_flow_test.go b/internal/bbsim/devices/onu_flow_test.go
index 4ff33a7..3f0d702 100644
--- a/internal/bbsim/devices/onu_flow_test.go
+++ b/internal/bbsim/devices/onu_flow_test.go
@@ -21,13 +21,12 @@
 	"github.com/looplab/fsm"
 	"github.com/opencord/voltha-protos/v2/go/openolt"
 	"gotest.tools/assert"
-	"sync"
 	"testing"
-	"time"
 )
 
+// test that BBR correctly sends the EAPOL Flow
 func Test_Onu_SendEapolFlow(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, false)
+	onu := createMockOnu(1, 1)
 
 	client := &mockClient{
 		FlowAddSpy: FlowAddSpy{
@@ -49,7 +48,7 @@
 
 // checks that the FlowId is added to the list
 func Test_HandleFlowAddFlowId(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, true, false)
+	onu := createMockOnu(1, 1)
 
 	flow := openolt.Flow{
 		FlowId:     64,
@@ -65,12 +64,110 @@
 	assert.Equal(t, onu.FlowIds[0], uint32(64))
 }
 
+// checks that we only remove the correct flow
+func Test_HandleFlowRemoveFlowId(t *testing.T) {
+	onu := createMockOnu(1, 1)
+
+	onu.FlowIds = []uint32{1, 2, 34, 64, 92}
+
+	flow := openolt.Flow{
+		FlowId:     64,
+		Classifier: &openolt.Classifier{},
+	}
+	msg := OnuFlowUpdateMessage{
+		OnuID:     onu.ID,
+		PonPortID: onu.PonPortID,
+		Flow:      &flow,
+	}
+	onu.handleFlowRemove(msg)
+	assert.Equal(t, len(onu.FlowIds), 4)
+	assert.Equal(t, onu.FlowIds[0], uint32(1))
+	assert.Equal(t, onu.FlowIds[1], uint32(2))
+	assert.Equal(t, onu.FlowIds[2], uint32(34))
+	assert.Equal(t, onu.FlowIds[3], uint32(92))
+}
+
+// checks that when the last flow is removed we reset the stored flags in the ONU
+func Test_HandleFlowRemoveFlowId_LastFlow(t *testing.T) {
+	onu := createMockOnu(1, 1)
+
+	onu.InternalState = fsm.NewFSM(
+		"enabled",
+		fsm.Events{
+			{Name: "disable", Src: []string{"enabled"}, Dst: "disabled"},
+		},
+		fsm.Callbacks{},
+	)
+
+	onu.GemPortAdded = true
+
+	onu.FlowIds = []uint32{64}
+
+	flow := openolt.Flow{
+		FlowId:     64,
+		Classifier: &openolt.Classifier{},
+	}
+	msg := OnuFlowUpdateMessage{
+		OnuID:     onu.ID,
+		PonPortID: onu.PonPortID,
+		Flow:      &flow,
+	}
+	onu.handleFlowRemove(msg)
+	assert.Equal(t, len(onu.FlowIds), 0)
+	assert.Equal(t, onu.GemPortAdded, false)
+}
+
+func TestOnu_HhandleEAPOLStart(t *testing.T) {
+	onu := createMockOnu(1, 1)
+	hsia := mockService{Name: "hsia"}
+	voip := mockService{Name: "voip"}
+
+	onu.Services = []ServiceIf{&hsia, &voip}
+
+	stream := mockStream{
+		Calls: make(map[int]*openolt.Indication),
+	}
+
+	onu.PonPort.Olt.OpenoltStream = &stream
+
+	flow := openolt.Flow{
+		AccessIntfId:  int32(onu.PonPortID),
+		OnuId:         int32(onu.ID),
+		UniId:         int32(0),
+		FlowId:        uint32(onu.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		Classifier: &openolt.Classifier{
+			EthType: uint32(layers.EthernetTypeEAPOL),
+			OVid:    4091,
+		},
+		Action:   &openolt.Action{},
+		Priority: int32(100),
+		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	msg := OnuFlowUpdateMessage{
+		PonPortID: 1,
+		OnuID:     1,
+		Flow:      &flow,
+	}
+
+	onu.handleFlowAdd(msg)
+
+	// check that we call HandleAuth on all the services
+	assert.Equal(t, hsia.HandleAuthCallCount, 1)
+	assert.Equal(t, voip.HandleAuthCallCount, 1)
+}
+
+// TODO all the following tests needs to be moved in the Service model
+
 // validates that when an ONU receives an EAPOL flow for UNI 0
 // and the GemPort has already been configured
 // it transition to auth_started state
 func Test_HandleFlowAddEapolWithGem(t *testing.T) {
-
-	onu := createMockOnu(1, 1, 900, 900, true, false)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"enabled",
@@ -110,8 +207,8 @@
 // validates that when an ONU receives an EAPOL flow for UNI that is not 0
 // no action is taken (this is independent of GemPort status
 func Test_HandleFlowAddEapolWrongUNI(t *testing.T) {
-
-	onu := createMockOnu(1, 1, 900, 900, true, false)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"enabled",
@@ -148,110 +245,12 @@
 	assert.Equal(t, onu.InternalState.Current(), "enabled")
 }
 
-// validates that when an ONU receives an EAPOL flow for UNI 0
-// and the GemPort has not yet been configured
-// it transition to auth_started state
-func Test_HandleFlowAddEapolWithoutGem(t *testing.T) {
-
-	onu := createMockOnu(1, 1, 900, 900, true, false)
-	onu.GemPortAdded = false
-
-	onu.InternalState = fsm.NewFSM(
-		"enabled",
-		fsm.Events{
-			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
-		},
-		fsm.Callbacks{},
-	)
-
-	flow := openolt.Flow{
-		AccessIntfId:  int32(onu.PonPortID),
-		OnuId:         int32(onu.ID),
-		UniId:         int32(0),
-		FlowId:        uint32(onu.ID),
-		FlowType:      "downstream",
-		AllocId:       int32(0),
-		NetworkIntfId: int32(0),
-		Classifier: &openolt.Classifier{
-			EthType: uint32(layers.EthernetTypeEAPOL),
-			OVid:    4091,
-		},
-		Action:   &openolt.Action{},
-		Priority: int32(100),
-		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
-	}
-
-	msg := OnuFlowUpdateMessage{
-		PonPortID: 1,
-		OnuID:     1,
-		Flow:      &flow,
-	}
-
-	onu.handleFlowAdd(msg)
-
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-	go func(wg *sync.WaitGroup) {
-		defer wg.Done()
-		time.Sleep(100 * time.Millisecond)
-
-		// emulate the addition of a GemPort
-		for _, ch := range onu.GemPortChannels {
-			ch <- true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-		assert.Equal(t, onu.InternalState.Current(), "auth_started")
-	}(&wg)
-	wg.Wait()
-
-}
-
-// validates that when an ONU receives an EAPOL flow for UNI 0
-// but the noAuth bit is set no action is taken
-func Test_HandleFlowAddEapolNoAuth(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, false)
-
-	onu.InternalState = fsm.NewFSM(
-		"enabled",
-		fsm.Events{
-			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
-		},
-		fsm.Callbacks{},
-	)
-
-	flow := openolt.Flow{
-		AccessIntfId:  int32(onu.PonPortID),
-		OnuId:         int32(onu.ID),
-		UniId:         int32(0),
-		FlowId:        uint32(onu.ID),
-		FlowType:      "downstream",
-		AllocId:       int32(0),
-		NetworkIntfId: int32(0),
-		Classifier: &openolt.Classifier{
-			EthType: uint32(layers.EthernetTypeEAPOL),
-			OVid:    4091,
-		},
-		Action:   &openolt.Action{},
-		Priority: int32(100),
-		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
-	}
-
-	msg := OnuFlowUpdateMessage{
-		PonPortID: 1,
-		OnuID:     1,
-		Flow:      &flow,
-	}
-
-	onu.handleFlowAdd(msg)
-	assert.Equal(t, onu.InternalState.Current(), "enabled")
-}
-
 // validates that when an ONU receives a DHCP flow for UNI 0 and pbit 0
 // and the GemPort has already been configured
 // it transition to dhcp_started state
 func Test_HandleFlowAddDhcp(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, true)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"eap_response_success_received",
@@ -288,14 +287,14 @@
 
 	onu.handleFlowAdd(msg)
 	assert.Equal(t, onu.InternalState.Current(), "dhcp_started")
-	assert.Equal(t, onu.DhcpFlowReceived, true)
 }
 
 // validates that when an ONU receives a DHCP flow for UNI 0 and pbit 255
 // and the GemPort has already been configured
 // it transition to dhcp_started state
 func Test_HandleFlowAddDhcpPBit255(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, true)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"eap_response_success_received",
@@ -332,14 +331,14 @@
 
 	onu.handleFlowAdd(msg)
 	assert.Equal(t, onu.InternalState.Current(), "dhcp_started")
-	assert.Equal(t, onu.DhcpFlowReceived, true)
 }
 
 // validates that when an ONU receives a DHCP flow for UNI 0 and pbit not 0 or 255
 // and the GemPort has already been configured
 // it ignores the message
 func Test_HandleFlowAddDhcpIgnoreByPbit(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, true)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"eap_response_success_received",
@@ -376,13 +375,13 @@
 
 	onu.handleFlowAdd(msg)
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
-	assert.Equal(t, onu.DhcpFlowReceived, false)
 }
 
 // validates that when an ONU receives a DHCP flow for UNI 0
 // but the noDchp bit is set no action is taken
 func Test_HandleFlowAddDhcpNoDhcp(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, false)
+	t.Skip("Needs to be moved in the Service struct")
+	onu := createMockOnu(1, 1)
 
 	onu.InternalState = fsm.NewFSM(
 		"eap_response_success_received",
@@ -418,16 +417,16 @@
 
 	onu.handleFlowAdd(msg)
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
-	assert.Equal(t, onu.DhcpFlowReceived, false)
 }
 
 // validates that when an ONU receives a DHCP flow for UNI 0 and pbit not 0 or 255
 // and the GemPort has not already been configured
 // it transition to dhcp_started state
 func Test_HandleFlowAddDhcpWithoutGem(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	// NOTE that this feature is required as there is no guarantee that the gemport is the same
 	// one we received with the EAPOL flow
-	onu := createMockOnu(1, 1, 900, 900, false, true)
+	onu := createMockOnu(1, 1)
 
 	onu.GemPortAdded = false
 
@@ -465,78 +464,4 @@
 	}
 
 	onu.handleFlowAdd(msg)
-
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-	go func(wg *sync.WaitGroup) {
-		defer wg.Done()
-		time.Sleep(100 * time.Millisecond)
-
-		// emulate the addition of a GemPort
-		for _, ch := range onu.GemPortChannels {
-			ch <- true
-		}
-
-		time.Sleep(100 * time.Millisecond)
-		assert.Equal(t, onu.InternalState.Current(), "dhcp_started")
-		assert.Equal(t, onu.DhcpFlowReceived, true)
-	}(&wg)
-	wg.Wait()
-}
-
-// checks that we only remove the correct flow
-func Test_HandleFlowRemoveFlowId(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, true, false)
-
-	onu.FlowIds = []uint32{1, 2, 34, 64, 92}
-
-	flow := openolt.Flow{
-		FlowId:     64,
-		Classifier: &openolt.Classifier{},
-	}
-	msg := OnuFlowUpdateMessage{
-		OnuID:     onu.ID,
-		PonPortID: onu.PonPortID,
-		Flow:      &flow,
-	}
-	onu.handleFlowRemove(msg)
-	assert.Equal(t, len(onu.FlowIds), 4)
-	assert.Equal(t, onu.FlowIds[0], uint32(1))
-	assert.Equal(t, onu.FlowIds[1], uint32(2))
-	assert.Equal(t, onu.FlowIds[2], uint32(34))
-	assert.Equal(t, onu.FlowIds[3], uint32(92))
-}
-
-// checks that when the last flow is removed we reset the stored flags in the ONU
-func Test_HandleFlowRemoveFlowId_LastFlow(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, true, false)
-
-	onu.InternalState = fsm.NewFSM(
-		"enabled",
-		fsm.Events{
-			{Name: "disable", Src: []string{"enabled"}, Dst: "disabled"},
-		},
-		fsm.Callbacks{},
-	)
-
-	onu.GemPortAdded = true
-	onu.DhcpFlowReceived = true
-	onu.EapolFlowReceived = true
-
-	onu.FlowIds = []uint32{64}
-
-	flow := openolt.Flow{
-		FlowId:     64,
-		Classifier: &openolt.Classifier{},
-	}
-	msg := OnuFlowUpdateMessage{
-		OnuID:     onu.ID,
-		PonPortID: onu.PonPortID,
-		Flow:      &flow,
-	}
-	onu.handleFlowRemove(msg)
-	assert.Equal(t, len(onu.FlowIds), 0)
-	assert.Equal(t, onu.GemPortAdded, false)
-	assert.Equal(t, onu.DhcpFlowReceived, false)
-	assert.Equal(t, onu.EapolFlowReceived, false)
 }
diff --git a/internal/bbsim/devices/onu_indications_test.go b/internal/bbsim/devices/onu_indications_test.go
index 433b776..76c7a7d 100644
--- a/internal/bbsim/devices/onu_indications_test.go
+++ b/internal/bbsim/devices/onu_indications_test.go
@@ -30,7 +30,7 @@
 type mockStream struct {
 	grpc.ServerStream
 	CallCount int
-	Calls     map[int]*openolt.OnuDiscIndication
+	Calls     map[int]*openolt.Indication
 	channel   chan int
 	fail      bool
 }
@@ -40,8 +40,10 @@
 	if s.fail {
 		return errors.New("fake-error")
 	}
-	s.Calls[s.CallCount] = ind.GetOnuDiscInd()
-	s.channel <- s.CallCount
+	s.Calls[s.CallCount] = ind
+	go func() {
+		s.channel <- s.CallCount
+	}()
 	return nil
 }
 
@@ -50,7 +52,7 @@
 	onu := createTestOnu()
 	stream := &mockStream{
 		CallCount: 0,
-		Calls:     make(map[int]*openolt.OnuDiscIndication),
+		Calls:     make(map[int]*openolt.Indication),
 		fail:      false,
 		channel:   make(chan int, 10),
 	}
@@ -62,9 +64,10 @@
 	select {
 	default:
 	case <-time.After(90 * time.Millisecond):
+		call := stream.Calls[1].GetOnuDiscInd()
 		assert.Equal(t, stream.CallCount, 1)
-		assert.Equal(t, stream.Calls[1].IntfId, onu.PonPortID)
-		assert.Equal(t, stream.Calls[1].SerialNumber, onu.SerialNumber)
+		assert.Equal(t, call.IntfId, onu.PonPortID)
+		assert.Equal(t, call.SerialNumber, onu.SerialNumber)
 	}
 	cancel()
 }
@@ -74,7 +77,7 @@
 	onu := createTestOnu()
 	stream := &mockStream{
 		CallCount: 0,
-		Calls:     make(map[int]*openolt.OnuDiscIndication),
+		Calls:     make(map[int]*openolt.Indication),
 		fail:      false,
 		channel:   make(chan int, 10),
 	}
@@ -97,7 +100,7 @@
 	onu.DiscoveryRetryDelay = 500 * time.Millisecond
 	stream := &mockStream{
 		CallCount: 0,
-		Calls:     make(map[int]*openolt.OnuDiscIndication),
+		Calls:     make(map[int]*openolt.Indication),
 		fail:      false,
 		channel:   make(chan int, 10),
 	}
diff --git a/internal/bbsim/devices/onu_state_machine_test.go b/internal/bbsim/devices/onu_state_machine_test.go
index fdecc3f..b48b286 100644
--- a/internal/bbsim/devices/onu_state_machine_test.go
+++ b/internal/bbsim/devices/onu_state_machine_test.go
@@ -37,8 +37,6 @@
 	assert.Equal(t, onu.InternalState.Current(), "enabled")
 
 	onu.PortNo = 16
-	onu.DhcpFlowReceived = true
-	onu.EapolFlowReceived = true
 	onu.GemPortAdded = true
 	onu.Flows = []FlowKey{
 		{ID: 1, Direction: "upstream"},
@@ -48,8 +46,6 @@
 	_ = onu.InternalState.Event("disable")
 	assert.Equal(t, onu.InternalState.Current(), "disabled")
 
-	assert.Equal(t, onu.DhcpFlowReceived, false)
-	assert.Equal(t, onu.EapolFlowReceived, false)
 	assert.Equal(t, onu.GemPortAdded, false)
 	assert.Equal(t, onu.PortNo, uint32(0))
 	assert.Equal(t, len(onu.Flows), 0)
@@ -59,6 +55,7 @@
 // - the GemPort is set
 // - the eapolFlow is received
 func Test_Onu_StateMachine_eapol_no_flow(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("enabled")
@@ -74,13 +71,13 @@
 }
 
 func Test_Onu_StateMachine_eapol_no_gem(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("enabled")
 	assert.Equal(t, onu.InternalState.Current(), "enabled")
-	// fail has no GemPort has been set
-	onu.EapolFlowReceived = true
 
+	// fail has no GemPort has been set
 	err := onu.InternalState.Event("start_auth")
 	if err == nil {
 		t.Fatal("can't start EAPOL without GemPort")
@@ -91,24 +88,23 @@
 }
 
 func Test_Onu_StateMachine_eapol_start(t *testing.T) {
-
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("enabled")
 	assert.Equal(t, onu.InternalState.Current(), "enabled")
 
 	// succeed
-	onu.EapolFlowReceived = true
 	onu.GemPortAdded = true
 	_ = onu.InternalState.Event("start_auth")
 	assert.Equal(t, onu.InternalState.Current(), "auth_started")
 }
 
 func Test_Onu_StateMachine_eapol_states(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.GemPortAdded = true
-	onu.EapolFlowReceived = true
 
 	onu.InternalState.SetState("auth_started")
 
@@ -134,13 +130,12 @@
 
 // if auth is set to true we can't go from enabled to dhcp_started
 func Test_Onu_StateMachine_dhcp_no_auth(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("enabled")
 	assert.Equal(t, onu.InternalState.Current(), "enabled")
 
-	onu.Auth = true
-
 	err := onu.InternalState.Event("start_dhcp")
 	if err == nil {
 		t.Fail()
@@ -151,13 +146,12 @@
 
 // if the DHCP flow has not been received we can't start authentication
 func Test_Onu_StateMachine_dhcp_no_flow(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("eap_response_success_received")
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
 
-	onu.DhcpFlowReceived = false
-
 	err := onu.InternalState.Event("start_dhcp")
 	if err == nil {
 		t.Fail()
@@ -168,12 +162,12 @@
 
 // if the ONU does not have a GemPort we can't start DHCP
 func Test_Onu_StateMachine_dhcp_no_gem(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
 	onu.InternalState.SetState("eap_response_success_received")
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
 
-	onu.DhcpFlowReceived = true
 	onu.GemPortAdded = false
 
 	err := onu.InternalState.Event("start_dhcp")
@@ -185,10 +179,9 @@
 }
 
 func Test_Onu_StateMachine_dhcp_start(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
-	onu.DhcpFlowReceived = true
 	onu.GemPortAdded = true
-	onu.Auth = true
 
 	onu.InternalState.SetState("eap_response_success_received")
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
@@ -199,9 +192,9 @@
 }
 
 func Test_Onu_StateMachine_dhcp_states(t *testing.T) {
+	t.Skip("Needs to be moved in the Service struct")
 	onu := createTestOnu()
 
-	onu.DhcpFlowReceived = true
 	onu.GemPortAdded = true
 
 	onu.InternalState.SetState("dhcp_started")
diff --git a/internal/bbsim/devices/onu_test.go b/internal/bbsim/devices/onu_test.go
index a3f1276..702b31a 100644
--- a/internal/bbsim/devices/onu_test.go
+++ b/internal/bbsim/devices/onu_test.go
@@ -17,8 +17,9 @@
 package devices
 
 import (
-	omcisim "github.com/opencord/omci-sim"
+	"github.com/google/gopacket/layers"
 	"gotest.tools/assert"
+	"net"
 	"testing"
 )
 
@@ -32,53 +33,71 @@
 		Olt: &olt,
 	}
 
-	onu := CreateONU(&olt, &pon, 1, 900, 900, true, false, 0, false)
+	onu := CreateONU(&olt, &pon, 1, 0, false)
 
 	assert.Equal(t, onu.Sn(), "BBSM00000101")
-	assert.Equal(t, onu.STag, 900)
-	assert.Equal(t, onu.CTag, 900)
-	assert.Equal(t, onu.Auth, true)
-	assert.Equal(t, onu.Dhcp, false)
-	assert.Equal(t, onu.HwAddress.String(), "2e:60:70:00:01:01")
 }
 
-func TestOnu_processOmciMessage_GemPortAdded(t *testing.T) {
+func Test_AddGemPortToService_eapol(t *testing.T) {
 
-	receivedValues := []bool{}
-
-	checker := func(ch chan bool, done chan int) {
-		for v := range ch {
-			receivedValues = append(receivedValues, v)
-		}
-		done <- 0
-	}
+	hsia := Service{Name: "hsia", NeedsEapol: true, CTag: 900}
+	voip := Service{Name: "voip", NeedsEapol: false, CTag: 55}
 
 	onu := createTestOnu()
 
-	// create two listeners on the GemPortAdded event
-	ch1 := onu.GetGemPortChan()
-	ch2 := onu.GetGemPortChan()
+	onu.Services = []ServiceIf{&hsia, &voip}
 
-	msg := omcisim.OmciChMessage{
-		Type: omcisim.GemPortAdded,
-		Data: omcisim.OmciChMessageData{
-			IntfId: 1,
-			OnuId:  1,
-		},
-	}
+	onu.addGemPortToService(1024, uint32(layers.EthernetTypeEAPOL), 0, 0)
 
-	onu.processOmciMessage(msg, nil)
+	assert.Equal(t, hsia.GemPort, uint32(1024))
+	assert.Equal(t, voip.GemPort, uint32(0))
+}
 
-	done := make(chan int)
+func Test_AddGemPortToService_dhcp(t *testing.T) {
 
-	go checker(ch1, done)
-	go checker(ch2, done)
+	hsia := Service{Name: "hsia", NeedsEapol: true}
+	voip := Service{Name: "voip", NeedsDhcp: true, CTag: 900}
+	mc := Service{Name: "mc", CTag: 900}
 
-	// wait for the messages to be received on the "done" channel
-	<-done
-	<-done
+	onu := createTestOnu()
 
-	// make sure all channel are closed and removed
-	assert.Equal(t, len(onu.GemPortChannels), 0)
-	assert.Equal(t, len(receivedValues), 2)
+	onu.Services = []ServiceIf{&hsia, &voip, &mc}
+
+	onu.addGemPortToService(1025, uint32(layers.EthernetTypeIPv4), 900, 0)
+
+	assert.Equal(t, hsia.GemPort, uint32(0))
+	assert.Equal(t, voip.GemPort, uint32(1025))
+	assert.Equal(t, mc.GemPort, uint32(0))
+}
+
+func Test_AddGemPortToService_dataplane(t *testing.T) {
+
+	hsia := Service{Name: "hsia", NeedsEapol: true, CTag: 900, STag: 500}
+	voip := Service{Name: "voip", NeedsDhcp: true, CTag: 900}
+
+	onu := createTestOnu()
+
+	onu.Services = []ServiceIf{&hsia, &voip}
+
+	onu.addGemPortToService(1024, uint32(layers.EthernetTypeLLC), 500, 900)
+
+	assert.Equal(t, hsia.GemPort, uint32(1024))
+	assert.Equal(t, voip.GemPort, uint32(0))
+}
+
+func Test_FindServiceByMacAddress(t *testing.T) {
+
+	mac := net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(2)}
+
+	hsia := Service{Name: "hsia", HwAddress: net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(1)}}
+	voip := Service{Name: "voip", HwAddress: mac}
+	vod := Service{Name: "vod", HwAddress: net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(3)}}
+
+	onu := createTestOnu()
+
+	onu.Services = []ServiceIf{&hsia, &voip, &vod}
+
+	service, err := onu.findServiceByMacAddress(mac)
+	assert.NilError(t, err)
+	assert.Equal(t, service.HwAddress.String(), mac.String())
 }
diff --git a/internal/bbsim/devices/onu_test_helpers.go b/internal/bbsim/devices/onu_test_helpers.go
index 5e4b584..76f7917 100644
--- a/internal/bbsim/devices/onu_test_helpers.go
+++ b/internal/bbsim/devices/onu_test_helpers.go
@@ -19,7 +19,6 @@
 import (
 	"context"
 	"errors"
-	"net"
 	"time"
 
 	"github.com/opencord/voltha-protos/v2/go/openolt"
@@ -107,19 +106,18 @@
 }
 
 // this method creates a fake ONU used in the tests
-func createMockOnu(id uint32, ponPortId uint32, sTag int, cTag int, auth bool, dhcp bool) *Onu {
+func createMockOnu(id uint32, ponPortId uint32) *Onu {
 	o := Onu{
 		ID:           id,
 		PonPortID:    ponPortId,
-		STag:         sTag,
-		CTag:         cTag,
-		HwAddress:    net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(ponPortId), byte(id)},
 		PortNo:       0,
-		Auth:         auth,
-		Dhcp:         dhcp,
 		GemPortAdded: true,
+		PonPort: &PonPort{
+			Olt: &OltDevice{},
+		},
 	}
 	o.SerialNumber = o.NewSN(0, ponPortId, o.ID)
+	o.Channel = make(chan Message, 10)
 	return &o
 }
 
@@ -132,7 +130,7 @@
 		ID:  1,
 		Olt: &olt,
 	}
-	onu := CreateONU(&olt, &pon, 1, 900, 900, false, false, time.Duration(1*time.Millisecond), true)
+	onu := CreateONU(&olt, &pon, 1, time.Duration(1*time.Millisecond), true)
 	// NOTE we need this in order to create the OnuChannel
 	_ = onu.InternalState.Event("initialize")
 	onu.DiscoveryRetryDelay = 100 * time.Millisecond
diff --git a/internal/bbsim/devices/service_test.go b/internal/bbsim/devices/service_test.go
new file mode 100644
index 0000000..f83193a
--- /dev/null
+++ b/internal/bbsim/devices/service_test.go
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package devices
+
+import (
+	"github.com/opencord/bbsim/internal/bbsim/types"
+	"github.com/opencord/voltha-protos/v2/go/openolt"
+	"gotest.tools/assert"
+	"net"
+	"testing"
+)
+
+type mockService struct {
+	Name                   string
+	HandleAuthCallCount    int
+	HandleDhcpCallCount    int
+	HandlePacketsCallCount int
+}
+
+func (s *mockService) HandleAuth(stream types.Stream) {
+	s.HandleAuthCallCount = s.HandleAuthCallCount + 1
+}
+
+func (s *mockService) HandleDhcp(stream types.Stream, cTag int) {
+	s.HandleDhcpCallCount = s.HandleDhcpCallCount + 1
+}
+
+func (s *mockService) HandlePackets(stream types.Stream) {
+	s.HandlePacketsCallCount = s.HandlePacketsCallCount + 1
+}
+
+func TestService_HandleAuth_noEapol(t *testing.T) {
+	mac := net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(1)}
+	onu := createMockOnu(1, 1)
+	s, err := NewService("testService", mac, onu, 900, 900,
+		false, false, false, 64, 0, false,
+		0, 0, 0, 0)
+
+	assert.NilError(t, err)
+
+	stream := &mockStream{
+		Calls:   make(map[int]*openolt.Indication),
+		channel: make(chan int, 10),
+	}
+
+	s.HandleAuth(stream)
+
+	// if the service does not need EAPOL we don't expect any packet to be generated
+	assert.Equal(t, stream.CallCount, 0)
+
+	// state should not change
+	assert.Equal(t, s.EapolState.Current(), "created")
+}
+
+func TestService_HandleAuth_withEapol(t *testing.T) {
+	mac := net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(1)}
+	onu := createMockOnu(1, 1)
+	s, err := NewService("testService", mac, onu, 900, 900,
+		true, false, false, 64, 0, false,
+		0, 0, 0, 0)
+
+	assert.NilError(t, err)
+
+	stream := &mockStream{
+		Calls: make(map[int]*openolt.Indication),
+	}
+
+	s.HandleAuth(stream)
+
+	// if the service does not need EAPOL we don't expect any packet to be generated
+	assert.Equal(t, stream.CallCount, 1)
+
+	// state should not change
+	assert.Equal(t, s.EapolState.Current(), "eap_start_sent")
+}
diff --git a/internal/bbsim/devices/services.go b/internal/bbsim/devices/services.go
new file mode 100644
index 0000000..c7940c9
--- /dev/null
+++ b/internal/bbsim/devices/services.go
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package devices
+
+import (
+	"github.com/looplab/fsm"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
+	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
+	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
+	bbsimTypes "github.com/opencord/bbsim/internal/bbsim/types"
+	log "github.com/sirupsen/logrus"
+	"net"
+)
+
+var serviceLogger = log.WithFields(log.Fields{
+	"module": "SERVICE",
+})
+
+type ServiceIf interface {
+	HandlePackets(stream bbsimTypes.Stream)        // start listening on the PacketCh
+	HandleAuth(stream bbsimTypes.Stream)           // Sends the EapoStart packet
+	HandleDhcp(stream bbsimTypes.Stream, cTag int) // Sends the DHCPDiscover packet
+}
+
+type Service struct {
+	Name                string
+	HwAddress           net.HardwareAddr
+	Onu                 *Onu
+	CTag                int
+	STag                int
+	NeedsEapol          bool
+	NeedsDhcp           bool
+	NeedsIgmp           bool
+	TechnologyProfileID int
+	UniTagMatch         int
+	ConfigureMacAddress bool
+	UsPonCTagPriority   int
+	UsPonSTagPriority   int
+	DsPonCTagPriority   int
+	DsPonSTagPriority   int
+
+	// state
+	GemPort    uint32
+	EapolState *fsm.FSM
+	DHCPState  *fsm.FSM
+	PacketCh   chan OnuPacketMessage
+}
+
+func NewService(name string, hwAddress net.HardwareAddr, onu *Onu, cTag int, sTag int,
+	needsEapol bool, needsDchp bool, needsIgmp bool, tpID int, uniTagMatch int, configMacAddress bool,
+	usPonCTagPriority int, usPonSTagPriority int, dsPonCTagPriority int, dsPonSTagPriority int) (*Service, error) {
+
+	service := Service{
+		Name:                name,
+		HwAddress:           hwAddress,
+		Onu:                 onu,
+		CTag:                cTag,
+		STag:                sTag,
+		NeedsEapol:          needsEapol,
+		NeedsDhcp:           needsDchp,
+		NeedsIgmp:           needsIgmp,
+		TechnologyProfileID: tpID,
+		UniTagMatch:         uniTagMatch,
+		ConfigureMacAddress: configMacAddress,
+		UsPonCTagPriority:   usPonCTagPriority,
+		UsPonSTagPriority:   usPonSTagPriority,
+		DsPonCTagPriority:   dsPonCTagPriority,
+		DsPonSTagPriority:   dsPonSTagPriority,
+		PacketCh:            make(chan OnuPacketMessage),
+	}
+
+	service.EapolState = fsm.NewFSM(
+		"created",
+		fsm.Events{
+			{Name: "start_auth", Src: []string{"created", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent", "eap_response_success_received", "auth_failed"}, Dst: "auth_started"},
+			{Name: "eap_start_sent", Src: []string{"auth_started"}, Dst: "eap_start_sent"},
+			{Name: "eap_response_identity_sent", Src: []string{"eap_start_sent"}, Dst: "eap_response_identity_sent"},
+			{Name: "eap_response_challenge_sent", Src: []string{"eap_response_identity_sent"}, Dst: "eap_response_challenge_sent"},
+			{Name: "eap_response_success_received", Src: []string{"eap_response_challenge_sent"}, Dst: "eap_response_success_received"},
+			{Name: "auth_failed", Src: []string{"auth_started", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent"}, Dst: "auth_failed"},
+		},
+		fsm.Callbacks{
+			"enter_state": func(e *fsm.Event) {
+				service.logStateChange("EapolState", e.Src, e.Dst)
+			},
+		},
+	)
+
+	service.DHCPState = fsm.NewFSM(
+		"created",
+		fsm.Events{
+			{Name: "start_dhcp", Src: []string{"created", "eap_response_success_received", "dhcp_discovery_sent", "dhcp_request_sent", "dhcp_ack_received", "dhcp_failed"}, Dst: "dhcp_started"},
+			{Name: "dhcp_discovery_sent", Src: []string{"dhcp_started"}, Dst: "dhcp_discovery_sent"},
+			{Name: "dhcp_request_sent", Src: []string{"dhcp_discovery_sent"}, Dst: "dhcp_request_sent"},
+			{Name: "dhcp_ack_received", Src: []string{"dhcp_request_sent"}, Dst: "dhcp_ack_received"},
+			{Name: "dhcp_failed", Src: []string{"dhcp_started", "dhcp_discovery_sent", "dhcp_request_sent"}, Dst: "dhcp_failed"},
+		},
+		fsm.Callbacks{
+			"enter_state": func(e *fsm.Event) {
+				service.logStateChange("DHCPState", e.Src, e.Dst)
+			},
+		},
+	)
+
+	return &service, nil
+}
+
+func (s *Service) HandleAuth(stream bbsimTypes.Stream) {
+
+	if !s.NeedsEapol {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":      s.Onu.ID,
+			"IntfId":     s.Onu.PonPortID,
+			"OnuSn":      s.Onu.Sn(),
+			"Name":       s.Name,
+			"NeedsEapol": s.NeedsEapol,
+		}).Debug("Won't start authentication as EAPOL is not required")
+		return
+	}
+
+	// TODO check if the EAPOL flow was received before starting auth
+
+	if err := s.EapolState.Event("start_auth"); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+			"err":    err.Error(),
+		}).Error("Can't start auth for this Service")
+	} else {
+		if err := s.handleEapolStart(stream); err != nil {
+			serviceLogger.WithFields(log.Fields{
+				"OnuId":  s.Onu.ID,
+				"IntfId": s.Onu.PonPortID,
+				"OnuSn":  s.Onu.Sn(),
+				"Name":   s.Name,
+				"err":    err,
+			}).Error("Error while sending EapolStart packet")
+			_ = s.EapolState.Event("auth_failed")
+		}
+	}
+}
+
+func (s *Service) HandleDhcp(stream bbsimTypes.Stream, cTag int) {
+
+	// FIXME start dhcp only for the Service that matches the tag
+	if s.CTag != cTag {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+		}).Debug("DHCP flow is not for this service, ignoring")
+		return
+	}
+
+	// NOTE since we're matching the flow tag, this may not be required
+	if !s.NeedsDhcp {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"Name":      s.Name,
+			"NeedsDhcp": s.NeedsDhcp,
+		}).Debug("Won't start DHCP as it is not required")
+		return
+	}
+
+	// TODO check if the EAPOL flow was received before starting auth
+
+	if err := s.DHCPState.Event("start_dhcp"); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+			"err":    err.Error(),
+		}).Error("Can't start DHCP for this Service")
+	} else {
+		if err := s.handleDHCPStart(stream); err != nil {
+			serviceLogger.WithFields(log.Fields{
+				"OnuId":  s.Onu.ID,
+				"IntfId": s.Onu.PonPortID,
+				"OnuSn":  s.Onu.Sn(),
+				"Name":   s.Name,
+				"err":    err,
+			}).Error("Error while sending DHCPDiscovery packet")
+			_ = s.DHCPState.Event("dhcp_failed")
+		}
+	}
+}
+
+func (s *Service) handleEapolStart(stream bbsimTypes.Stream) error {
+
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":   s.Onu.ID,
+		"IntfId":  s.Onu.PonPortID,
+		"OnuSn":   s.Onu.Sn(),
+		"GemPort": s.GemPort,
+		"Name":    s.Name,
+	}).Debugf("handleEapolStart")
+
+	if err := eapol.SendEapStart(s.Onu.ID, s.Onu.PonPortID, s.Onu.Sn(), s.Onu.PortNo,
+		s.HwAddress, s.GemPort, s.EapolState, stream); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":   s.Onu.ID,
+			"IntfId":  s.Onu.PonPortID,
+			"OnuSn":   s.Onu.Sn(),
+			"GemPort": s.GemPort,
+			"Name":    s.Name,
+		}).Error("handleEapolStart")
+		return err
+	}
+	return nil
+}
+
+func (s *Service) handleDHCPStart(stream bbsimTypes.Stream) error {
+
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":     s.Onu.ID,
+		"IntfId":    s.Onu.PonPortID,
+		"OnuSn":     s.Onu.Sn(),
+		"Name":      s.Name,
+		"GemPortId": s.GemPort,
+	}).Debugf("HandleDHCPStart")
+
+	if err := dhcp.SendDHCPDiscovery(s.Onu.PonPort.Olt.ID, s.Onu.PonPortID, s.Onu.ID, int(s.CTag), s.GemPort,
+		s.Onu.Sn(), s.Onu.PortNo, s.DHCPState, s.HwAddress, stream); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"Name":      s.Name,
+			"GemPortId": s.GemPort,
+		}).Error("HandleDHCPStart")
+		return err
+	}
+	return nil
+}
+
+func (s *Service) HandlePackets(stream bbsimTypes.Stream) {
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":     s.Onu.ID,
+		"IntfId":    s.Onu.PonPortID,
+		"OnuSn":     s.Onu.Sn(),
+		"GemPortId": s.GemPort,
+		"Name":      s.Name,
+	}).Debug("Listening on Service Packet Channel")
+
+	defer func() {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"GemPortId": s.GemPort,
+			"Name":      s.Name,
+		}).Debug("Done Listening on Service Packet Channel")
+	}()
+
+	for msg := range s.PacketCh {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":       s.Onu.ID,
+			"IntfId":      s.Onu.PonPortID,
+			"OnuSn":       s.Onu.Sn(),
+			"Name":        s.Name,
+			"messageType": msg.Type,
+		}).Debug("Received message on Service Packet Channel")
+
+		if msg.Type == packetHandlers.EAPOL {
+			eapol.HandleNextPacket(msg.OnuId, msg.IntfId, s.GemPort, s.Onu.Sn(), s.Onu.PortNo, s.EapolState, msg.Packet, stream, nil)
+		} else if msg.Type == packetHandlers.DHCP {
+			_ = dhcp.HandleNextPacket(s.Onu.PonPort.Olt.ID, s.Onu.ID, s.Onu.PonPortID, s.Onu.Sn(), s.Onu.PortNo, s.CTag, s.GemPort, s.HwAddress, s.DHCPState, msg.Packet, stream)
+		}
+	}
+}
+
+func (s *Service) logStateChange(stateMachine string, src string, dst string) {
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":  s.Onu.ID,
+		"IntfId": s.Onu.PonPortID,
+		"OnuSn":  s.Onu.Sn(),
+		"Name":   s.Name,
+	}).Debugf("Changing Service.%s InternalState from %s to %s", stateMachine, src, dst)
+}
diff --git a/internal/bbsim/responders/dhcp/dhcp.go b/internal/bbsim/responders/dhcp/dhcp.go
index 227ce8e..a0f5fff 100644
--- a/internal/bbsim/responders/dhcp/dhcp.go
+++ b/internal/bbsim/responders/dhcp/dhcp.go
@@ -29,13 +29,10 @@
 	"github.com/looplab/fsm"
 	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	bbsim "github.com/opencord/bbsim/internal/bbsim/types"
-	omci "github.com/opencord/omci-sim"
 	"github.com/opencord/voltha-protos/v2/go/openolt"
 	log "github.com/sirupsen/logrus"
 )
 
-var GetGemPortId = omci.GetGemPortId
-
 var dhcpLogger = log.WithFields(log.Fields{
 	"module": "DHCP",
 })
@@ -98,7 +95,7 @@
 	return opts
 }
 
-func createDHCPDisc(oltId int, intfId uint32, onuId uint32, macAddress net.HardwareAddr) *layers.DHCPv4 {
+func createDHCPDisc(oltId int, intfId uint32, onuId uint32, gemPort uint32, macAddress net.HardwareAddr) *layers.DHCPv4 {
 	dhcpLayer := createDefaultDHCPReq(oltId, intfId, onuId, macAddress)
 	defaultOpts := createDefaultOpts(intfId, onuId)
 	dhcpLayer.Options = append([]layers.DHCPOption{{
@@ -108,7 +105,7 @@
 	}}, defaultOpts...)
 
 	data := []byte{0xcd, 0x28, 0xcb, 0xcc, 0x00, 0x01, 0x00, 0x01,
-		0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, 0x28, byte(intfId), byte(onuId)} //FIXME use the OLT-ID in here
+		0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, byte(intfId), byte(onuId), byte(gemPort)} //FIXME use the OLT-ID in here
 	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
 		Type:   layers.DHCPOptClientID,
 		Data:   data,
@@ -242,28 +239,19 @@
 	return dhcpMessageType.String(), nil
 }
 
-func sendDHCPPktIn(msg bbsim.ByteMsg, portNo uint32, stream bbsim.Stream) error {
-	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
-	gemid, err := GetGemPortId(msg.IntfId, msg.OnuId)
-	if err != nil {
-		dhcpLogger.WithFields(log.Fields{
-			"OnuId":  msg.OnuId,
-			"IntfId": msg.IntfId,
-		}).Errorf("Can't retrieve GemPortId: %s", err)
-		return err
-	}
+func sendDHCPPktIn(msg bbsim.ByteMsg, portNo uint32, gemPortId uint32, stream bbsim.Stream) error {
 
 	log.WithFields(log.Fields{
 		"OnuId":   msg.OnuId,
 		"IntfId":  msg.IntfId,
-		"GemPort": gemid,
+		"GemPort": gemPortId,
 		"Type":    "DHCP",
 	}).Trace("sending-pkt")
 
 	data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
 		IntfType:  "pon",
 		IntfId:    msg.IntfId,
-		GemportId: uint32(gemid),
+		GemportId: gemPortId,
 		Pkt:       msg.Bytes,
 		PortNo:    portNo,
 	}}
@@ -275,7 +263,7 @@
 	return nil
 }
 
-func sendDHCPRequest(oltId int, ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, cTag int, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, offeredIp net.IP, stream openolt.Openolt_EnableIndicationServer) error {
+func sendDHCPRequest(oltId int, ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, cTag int, gemPortId uint32, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, offeredIp net.IP, stream bbsim.Stream) error {
 	dhcp := createDHCPReq(oltId, ponPortId, onuId, onuHwAddress, offeredIp)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, cTag, onuHwAddress, dhcp)
 
@@ -300,7 +288,7 @@
 		Bytes:  pkt,
 	}
 
-	if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
+	if err := sendDHCPPktIn(msg, portNo, gemPortId, stream); err != nil {
 		dhcpLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
@@ -333,8 +321,8 @@
 	return nil
 }
 
-func SendDHCPDiscovery(oltId int, ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, cTag int, stream bbsim.Stream) error {
-	dhcp := createDHCPDisc(oltId, ponPortId, onuId, onuHwAddress)
+func SendDHCPDiscovery(oltId int, ponPortId uint32, onuId uint32, cTag int, gemPortId uint32, serialNumber string, portNo uint32, stateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, stream bbsim.Stream) error {
+	dhcp := createDHCPDisc(oltId, ponPortId, onuId, gemPortId, onuHwAddress)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, cTag, onuHwAddress, dhcp)
 
 	if err != nil {
@@ -343,7 +331,7 @@
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
 		}).Errorf("Cannot serializeDHCPPacket: %s", err)
-		if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
+		if err := updateDhcpFailed(onuId, ponPortId, serialNumber, stateMachine); err != nil {
 			return err
 		}
 		return err
@@ -357,13 +345,13 @@
 		Bytes:  pkt,
 	}
 
-	if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
+	if err := sendDHCPPktIn(msg, portNo, gemPortId, stream); err != nil {
 		dhcpLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
 		}).Errorf("Cannot sendDHCPPktIn: %s", err)
-		if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
+		if err := updateDhcpFailed(onuId, ponPortId, serialNumber, stateMachine); err != nil {
 			return err
 		}
 		return err
@@ -374,7 +362,7 @@
 		"OnuSn":  serialNumber,
 	}).Infof("DHCPDiscovery Sent")
 
-	if err := onuStateMachine.Event("dhcp_discovery_sent"); err != nil {
+	if err := stateMachine.Event("dhcp_discovery_sent"); err != nil {
 		dhcpLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
@@ -385,7 +373,7 @@
 }
 
 // FIXME cTag is not used here
-func HandleNextPacket(oltId int, onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, cTag int, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) error {
+func HandleNextPacket(oltId int, onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, cTag int, gemPortId uint32, onuHwAddress net.HardwareAddr, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream bbsim.Stream) error {
 
 	dhcpLayer, err := GetDhcpLayer(pkt)
 	if err != nil {
@@ -415,7 +403,7 @@
 	if dhcpLayer.Operation == layers.DHCPOpReply {
 		if dhcpMessageType == layers.DHCPMsgTypeOffer {
 			offeredIp := dhcpLayer.YourClientIP
-			if err := sendDHCPRequest(oltId, ponPortId, onuId, serialNumber, portNo, cTag, onuStateMachine, onuHwAddress, offeredIp, stream); err != nil {
+			if err := sendDHCPRequest(oltId, ponPortId, onuId, serialNumber, portNo, cTag, gemPortId, onuStateMachine, onuHwAddress, offeredIp, stream); err != nil {
 				dhcpLogger.WithFields(log.Fields{
 					"OnuId":  onuId,
 					"IntfId": ponPortId,
@@ -463,7 +451,7 @@
 
 // This method handle the BBR DHCP Packets
 // BBR does not need to do anything but forward the packets in the correct direction
-func HandleNextBbrPacket(onuId uint32, ponPortId uint32, serialNumber string, sTag int, macAddress net.HardwareAddr, doneChannel chan bool, pkt gopacket.Packet, client openolt.OpenoltClient) error {
+func HandleNextBbrPacket(onuId uint32, ponPortId uint32, serialNumber string, doneChannel chan bool, pkt gopacket.Packet, client openolt.OpenoltClient) error {
 
 	// check if the packet is going:
 	// - outgouing: toward the DHCP
@@ -521,7 +509,6 @@
 			"Type":   dhcpType,
 			"DstMac": dstMac,
 			"SrcMac": srcMac,
-			"OnuMac": macAddress,
 		}).Infof("Sent DHCP packet to the ONU")
 
 		dhcpLayer, _ := GetDhcpLayer(pkt)
@@ -532,8 +519,9 @@
 
 	} else {
 		// double tag the packet and send it to the NNI
-		// NOTE do we need this in the HandleDHCP Packet?
-		doubleTaggedPkt, err := packetHandlers.PushDoubleTag(sTag, sTag, pkt)
+		// we don't really care about the tags as they are stripped before
+		// the packet is sent to the DHCP server
+		doubleTaggedPkt, err := packetHandlers.PushDoubleTag(900, 900, pkt)
 		if err != nil {
 			dhcpLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
@@ -564,7 +552,6 @@
 			"Type":   dhcpType,
 			"DstMac": dstMac,
 			"SrcMac": srcMac,
-			"OnuMac": macAddress,
 		}).Infof("Sent DHCP packet out of the NNI Port")
 	}
 	return nil
diff --git a/internal/bbsim/responders/dhcp/dhcp_test.go b/internal/bbsim/responders/dhcp/dhcp_test.go
index ff9b943..cc50188 100644
--- a/internal/bbsim/responders/dhcp/dhcp_test.go
+++ b/internal/bbsim/responders/dhcp/dhcp_test.go
@@ -62,27 +62,19 @@
 	dhcpStateMachine.SetState("dhcp_started")
 
 	var onuId uint32 = 1
-	var gemPortId uint16 = 1
+	var gemPortId uint32 = 1
 	var ponPortId uint32 = 0
 	var oltId int = 1
 	var serialNumber = "BBSM00000001"
 	var mac = net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(ponPortId), byte(onuId)}
 	var portNo uint32 = 16
 
-	// Save current function and restore at the end:
-	old := GetGemPortId
-	defer func() { GetGemPortId = old }()
-
-	GetGemPortId = func(intfId uint32, onuId uint32) (uint16, error) {
-		return gemPortId, nil
-	}
-
 	stream := &mockStreamSuccess{
 		Calls: make(map[int]*openolt.PacketIndication),
 		fail:  false,
 	}
 
-	if err := SendDHCPDiscovery(oltId, ponPortId, onuId, serialNumber, portNo, dhcpStateMachine, mac, 1, stream); err != nil {
+	if err := SendDHCPDiscovery(oltId, ponPortId, onuId, 900, gemPortId, serialNumber, portNo, dhcpStateMachine, mac, stream); err != nil {
 		t.Errorf("SendDHCPDiscovery returned an error: %v", err)
 		t.Fail()
 	}
diff --git a/internal/bbsim/responders/eapol/eapol.go b/internal/bbsim/responders/eapol/eapol.go
index cd0606e..5595d5b 100644
--- a/internal/bbsim/responders/eapol/eapol.go
+++ b/internal/bbsim/responders/eapol/eapol.go
@@ -38,16 +38,8 @@
 var eapolVersion uint8 = 1
 var GetGemPortId = omci.GetGemPortId
 
-func sendEapolPktIn(msg bbsim.ByteMsg, portNo uint32, stream openolt.Openolt_EnableIndicationServer) {
+func sendEapolPktIn(msg bbsim.ByteMsg, portNo uint32, gemid uint32, stream bbsim.Stream) {
 	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
-	gemid, err := omci.GetGemPortId(msg.IntfId, msg.OnuId)
-	if err != nil {
-		eapolLogger.WithFields(log.Fields{
-			"OnuId":  msg.OnuId,
-			"IntfId": msg.IntfId,
-		}).Errorf("Can't retrieve GemPortId: %s", err)
-		return
-	}
 
 	log.WithFields(log.Fields{
 		"OnuId":   msg.OnuId,
@@ -137,8 +129,8 @@
 	options := gopacket.SerializeOptions{}
 
 	ethernetLayer := &layers.Ethernet{
-		SrcMAC:       net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(intfId), byte(onuId)},
-		DstMAC:       net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03},
+		SrcMAC:       net.HardwareAddr{0x2e, 0x60, byte(0), byte(intfId), byte(onuId), byte(0)},
+		DstMAC:       net.HardwareAddr{0x2e, 0x60, byte(0), byte(intfId), byte(onuId), byte(0)},
 		EthernetType: layers.EthernetTypeEAPOL,
 	}
 
@@ -197,22 +189,7 @@
 	return nil
 }
 
-func SendEapStart(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, macAddress net.HardwareAddr, onuStateMachine *fsm.FSM, stream bbsim.Stream) error {
-
-	// send the packet (hacked together)
-	gemId, err := GetGemPortId(ponPortId, onuId)
-	if err != nil {
-		eapolLogger.WithFields(log.Fields{
-			"OnuId":  onuId,
-			"IntfId": ponPortId,
-			"OnuSn":  serialNumber,
-		}).Errorf("Can't retrieve GemPortId: %s", err)
-
-		if err := updateAuthFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
-			return err
-		}
-		return err
-	}
+func SendEapStart(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, macAddress net.HardwareAddr, gemPort uint32, stateMachine *fsm.FSM, stream bbsim.Stream) error {
 
 	// TODO use createEAPOLPkt
 	buffer := gopacket.NewSerializeBuffer()
@@ -236,13 +213,13 @@
 		PktInd: &openolt.PacketIndication{
 			IntfType:  "pon",
 			IntfId:    ponPortId,
-			GemportId: uint32(gemId),
+			GemportId: gemPort,
 			Pkt:       msg,
 			PortNo:    portNo,
 		},
 	}
 
-	err = stream.Send(&openolt.Indication{Data: data})
+	err := stream.Send(&openolt.Indication{Data: data})
 	if err != nil {
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
@@ -250,7 +227,7 @@
 			"OnuSn":  serialNumber,
 		}).Errorf("Can't send EapStart Message: %s", err)
 
-		if err := updateAuthFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
+		if err := updateAuthFailed(onuId, ponPortId, serialNumber, stateMachine); err != nil {
 			return err
 		}
 		return err
@@ -263,7 +240,7 @@
 		"PortNo": portNo,
 	}).Debugf("Sent EapStart packet")
 
-	if err := onuStateMachine.Event("eap_start_sent"); err != nil {
+	if err := stateMachine.Event("eap_start_sent"); err != nil {
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
@@ -274,7 +251,7 @@
 	return nil
 }
 
-func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer, client openolt.OpenoltClient) {
+func HandleNextPacket(onuId uint32, ponPortId uint32, gemPortId uint32, serialNumber string, portNo uint32, stateMachine *fsm.FSM, pkt gopacket.Packet, stream bbsim.Stream, client openolt.OpenoltClient) {
 
 	eap, eapErr := extractEAP(pkt)
 
@@ -335,14 +312,14 @@
 			Bytes:  pkt,
 		}
 
-		sendEapolPktIn(msg, portNo, stream)
+		sendEapolPktIn(msg, portNo, gemPortId, stream)
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
 			"PortNo": portNo,
 		}).Debugf("Sent EAPIdentityResponse packet")
-		if err := onuStateMachine.Event("eap_response_identity_sent"); err != nil {
+		if err := stateMachine.Event("eap_response_identity_sent"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
 				"IntfId": ponPortId,
@@ -383,14 +360,14 @@
 			Bytes:  pkt,
 		}
 
-		sendEapolPktIn(msg, portNo, stream)
+		sendEapolPktIn(msg, portNo, gemPortId, stream)
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
 			"PortNo": portNo,
 		}).Debugf("Sent EAPChallengeResponse packet")
-		if err := onuStateMachine.Event("eap_response_challenge_sent"); err != nil {
+		if err := stateMachine.Event("eap_response_challenge_sent"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
 				"IntfId": ponPortId,
@@ -417,7 +394,7 @@
 			"OnuSn":  serialNumber,
 		}).Infof("Sent EAP Success packet")
 
-		if err := onuStateMachine.Event("send_dhcp_flow"); err != nil {
+		if err := stateMachine.Event("send_dhcp_flow"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
 				"IntfId": ponPortId,
@@ -431,7 +408,7 @@
 			"OnuSn":  serialNumber,
 			"PortNo": portNo,
 		}).Debugf("Received EAPSuccess packet")
-		if err := onuStateMachine.Event("eap_response_success_received"); err != nil {
+		if err := stateMachine.Event("eap_response_success_received"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
 				"IntfId": ponPortId,
diff --git a/internal/bbsim/responders/eapol/eapol_test.go b/internal/bbsim/responders/eapol/eapol_test.go
index 9f8ff22..824ce65 100644
--- a/internal/bbsim/responders/eapol/eapol_test.go
+++ b/internal/bbsim/responders/eapol/eapol_test.go
@@ -43,7 +43,7 @@
 
 // params for the function under test
 var onuId uint32 = 1
-var gemPortId uint16 = 1
+var gemPortId uint32 = 1
 var ponPortId uint32 = 0
 var serialNumber string = "BBSM00000001"
 var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
@@ -70,20 +70,12 @@
 func TestSendEapStartSuccess(t *testing.T) {
 	eapolStateMachine.SetState("auth_started")
 
-	// Save current function and restore at the end:
-	old := GetGemPortId
-	defer func() { GetGemPortId = old }()
-
-	GetGemPortId = func(intfId uint32, onuId uint32) (uint16, error) {
-		return gemPortId, nil
-	}
-
 	stream := &mockStream{
 		Calls: make(map[int]*openolt.PacketIndication),
 		fail:  false,
 	}
 
-	if err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream); err != nil {
+	if err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, gemPortId, eapolStateMachine, stream); err != nil {
 		t.Errorf("SendEapStart returned an error: %v", err)
 		t.Fail()
 	}
@@ -98,35 +90,6 @@
 
 }
 
-func TestSendEapStartFailNoGemPort(t *testing.T) {
-	eapolStateMachine.SetState("auth_started")
-
-	// Save current function and restore at the end:
-	old := GetGemPortId
-	defer func() { GetGemPortId = old }()
-
-	GetGemPortId = func(intfId uint32, onuId uint32) (uint16, error) {
-		return 0, errors.New("no-gem-port")
-	}
-
-	var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
-
-	stream := &mockStream{
-		Calls: make(map[int]*openolt.PacketIndication),
-		fail:  false,
-	}
-
-	err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream)
-	if err == nil {
-		t.Errorf("SendEapStart did not return an error")
-		t.Fail()
-	}
-
-	assert.Equal(t, err.Error(), "no-gem-port")
-
-	assert.Equal(t, eapolStateMachine.Current(), "auth_failed")
-}
-
 func TestSendEapStartFailStreamError(t *testing.T) {
 
 	eapolStateMachine.SetState("auth_started")
@@ -144,7 +107,7 @@
 		fail:  true,
 	}
 
-	err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream)
+	err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, gemPortId, eapolStateMachine, stream)
 	if err == nil {
 		t.Errorf("SendEapStart did not return an error")
 		t.Fail()
diff --git a/internal/bbsim/responders/sadis/sadis.go b/internal/bbsim/responders/sadis/sadis.go
index 75a6fd9..444fd57 100644
--- a/internal/bbsim/responders/sadis/sadis.go
+++ b/internal/bbsim/responders/sadis/sadis.go
@@ -76,27 +76,16 @@
 	UplinkPort         int    `json:"uplinkPort"`
 }
 
-type SadisOnuEntry struct {
-	ID                         string `json:"id"`
-	CTag                       int    `json:"cTag"`
-	STag                       int    `json:"sTag"`
-	NasPortID                  string `json:"nasPortId"`
-	CircuitID                  string `json:"circuitId"`
-	RemoteID                   string `json:"remoteId"`
-	TechnologyProfileID        int    `json:"technologyProfileId"`
-	UpstreamBandwidthProfile   string `json:"upstreamBandwidthProfile"`
-	DownstreamBandwidthProfile string `json:"downstreamBandwidthProfile"`
-}
-
 type SadisOnuEntryV2 struct {
 	ID         string        `json:"id"`
 	NasPortID  string        `json:"nasPortId"`
 	CircuitID  string        `json:"circuitId"`
 	RemoteID   string        `json:"remoteId"`
-	UniTagList []interface{} `json:"uniTagList"` // this can be SadisUniTagAtt, SadisUniTagDt
+	UniTagList []SadisUniTag `json:"uniTagList"` // this can be SadisUniTagAtt, SadisUniTagDt
 }
 
-type SadisUniTagAtt struct {
+type SadisUniTag struct {
+	UniTagMatch                int    `json:"uniTagMatch,omitempty"`
 	PonCTag                    int    `json:"ponCTag,omitempty"`
 	PonSTag                    int    `json:"ponSTag,omitempty"`
 	TechnologyProfileID        int    `json:"technologyProfileId,omitempty"`
@@ -104,15 +93,12 @@
 	DownstreamBandwidthProfile string `json:"downstreamBandwidthProfile,omitempty"`
 	IsDhcpRequired             bool   `json:"isDhcpRequired,omitempty"`
 	IsIgmpRequired             bool   `json:"isIgmpRequired,omitempty"`
-}
-
-type SadisUniTagDt struct {
-	UniTagMatch                int    `json:"uniTagMatch,omitempty"`
-	PonCTag                    int    `json:"ponCTag,omitempty"`
-	PonSTag                    int    `json:"ponSTag,omitempty"`
-	TechnologyProfileID        int    `json:"technologyProfileId,omitempty"`
-	UpstreamBandwidthProfile   string `json:"upstreamBandwidthProfile,omitempty"`
-	DownstreamBandwidthProfile string `json:"downstreamBandwidthProfile,omitempty"`
+	ConfiguredMacAddress       string `json:"configuredMacAddress,omitempty"`
+	UsPonCTagPriority          int    `json:"usPonCTagPriority,omitempty"`
+	UsPonSTagPriority          int    `json:"usPonSTagPriority,omitempty"`
+	DsPonCTagPriority          int    `json:"dsPonCTagPriority,omitempty"`
+	DsPonSTagPriority          int    `json:"dsPonSTagPriority,omitempty"`
+	ServiceName                string `json:"serviceName,omitempty"`
 }
 
 // SADIS BandwithProfile Entry
@@ -143,7 +129,7 @@
 	entries := []interface{}{}
 	entries = append(entries, solt)
 
-	a := strings.Split(common.Options.BBSim.SadisRestAddress, ":")
+	a := strings.Split(common.Config.BBSim.SadisRestAddress, ":")
 	port := a[len(a)-1]
 
 	integration := SadisIntegration{}
@@ -164,7 +150,7 @@
 	ip, _ := common.GetIPAddr("nni") // TODO verify which IP to report
 	solt := &SadisOltEntry{
 		ID:                 olt.SerialNumber,
-		HardwareIdentifier: common.Options.Olt.DeviceId,
+		HardwareIdentifier: common.Config.Olt.DeviceId,
 		IPAddress:          ip,
 		NasID:              olt.SerialNumber,
 		UplinkPort:         1048576, // TODO currently assumes we only have one NNI port
@@ -172,22 +158,6 @@
 	return solt, nil
 }
 
-func GetOnuEntryV1(olt *devices.OltDevice, onu *devices.Onu, uniId string) (*SadisOnuEntry, error) {
-	uniSuffix := "-" + uniId
-	sonu := &SadisOnuEntry{
-		ID:                         onu.Sn() + uniSuffix,
-		CTag:                       onu.CTag,
-		STag:                       onu.STag,
-		NasPortID:                  onu.Sn() + uniSuffix,
-		CircuitID:                  onu.Sn() + uniSuffix,
-		RemoteID:                   olt.SerialNumber,
-		TechnologyProfileID:        64,
-		UpstreamBandwidthProfile:   "User_Bandwidth1",
-		DownstreamBandwidthProfile: "Default",
-	}
-	return sonu, nil
-}
-
 func GetOnuEntryV2(olt *devices.OltDevice, onu *devices.Onu, uniId string) (*SadisOnuEntryV2, error) {
 	uniSuffix := "-" + uniId
 
@@ -198,42 +168,54 @@
 		RemoteID:  onu.Sn() + uniSuffix,
 	}
 
-	// base structure common to all use cases
-	var sonuUniTag interface{}
+	// createUniTagList
+	for _, s := range onu.Services {
 
-	// set workflow specific params
-	switch common.Options.BBSim.SadisFormat {
-	case common.SadisFormatAtt:
-		sonuUniTag = SadisUniTagAtt{
-			PonCTag:             onu.CTag,
-			PonSTag:             onu.STag,
-			TechnologyProfileID: 64,
-			// NOTE do we want to select a random bandwidth profile?
-			// if so use bandwidthProfiles[rand.Intn(len(bandwidthProfiles))].ID
-			UpstreamBandwidthProfile:   "Default",
-			DownstreamBandwidthProfile: "User_Bandwidth1",
-			IsDhcpRequired:             common.Options.BBSim.EnableDhcp,
-			IsIgmpRequired:             common.Options.BBSim.EnableIgmp,
+		service := s.(*devices.Service)
+
+		tag := SadisUniTag{
+			ServiceName:                service.Name,
+			IsIgmpRequired:             service.NeedsIgmp,
+			IsDhcpRequired:             service.NeedsDhcp,
+			TechnologyProfileID:        service.TechnologyProfileID,
+			UpstreamBandwidthProfile:   "User_Bandwidth1",
+			DownstreamBandwidthProfile: "User_Bandwidth2",
+			PonCTag:                    service.CTag,
+			PonSTag:                    service.STag,
 		}
-	case common.SadisFormatDt:
-		sonuUniTag = SadisUniTagDt{
-			PonCTag:             4096,
-			PonSTag:             onu.STag,
-			TechnologyProfileID: 64,
-			// NOTE do we want to select a random bandwidth profile?
-			// if so use bandwidthProfiles[rand.Intn(len(bandwidthProfiles))].ID
-			UpstreamBandwidthProfile:   "Default",
-			DownstreamBandwidthProfile: "User_Bandwidth1",
-			UniTagMatch:                4096,
+
+		if service.UniTagMatch != 0 {
+			tag.UniTagMatch = service.UniTagMatch
 		}
+
+		if service.ConfigureMacAddress {
+			tag.ConfiguredMacAddress = service.HwAddress.String()
+		}
+
+		if service.UsPonCTagPriority != 0 {
+			tag.UsPonCTagPriority = service.UsPonCTagPriority
+		}
+
+		if service.UsPonSTagPriority != 0 {
+			tag.UsPonSTagPriority = service.UsPonSTagPriority
+		}
+
+		if service.DsPonCTagPriority != 0 {
+			tag.DsPonCTagPriority = service.DsPonCTagPriority
+		}
+
+		if service.DsPonSTagPriority != 0 {
+			tag.DsPonSTagPriority = service.DsPonSTagPriority
+		}
+
+		sonuv2.UniTagList = append(sonuv2.UniTagList, tag)
 	}
 
-	sonuv2.UniTagList = append(sonuv2.UniTagList, sonuUniTag)
 	return sonuv2, nil
 }
 
 func getBWPEntries(version string) *BandwidthProfileEntries {
-	a := strings.Split(common.Options.BBSim.SadisRestAddress, ":")
+	a := strings.Split(common.Config.BBSim.SadisRestAddress, ":")
 	port := a[len(a)-1]
 
 	integration := SadisIntegration{}
@@ -278,14 +260,9 @@
 	sadisConf.Sadis.Integration.URL = ""
 	for i := range s.olt.Pons {
 		for _, onu := range s.olt.Pons[i].Onus {
-			// FIXME currently we only support one UNI per ONU
-			if vars["version"] == "v1" {
-				sonuV1, _ := GetOnuEntryV1(s.olt, onu, "1")
-				sadisConf.Sadis.Entries = append(sadisConf.Sadis.Entries, sonuV1)
-			} else if vars["version"] == "v2" {
+			if vars["version"] == "v2" {
 				sonuV2, _ := GetOnuEntryV2(s.olt, onu, "1")
 				sadisConf.Sadis.Entries = append(sadisConf.Sadis.Entries, sonuV2)
-
 			}
 		}
 	}
@@ -343,11 +320,12 @@
 		"OnuPortNo": uni,
 	}).Debug("Received SADIS request")
 
-	w.WriteHeader(http.StatusOK)
 	if vars["version"] == "v1" {
-		sadisConf, _ := GetOnuEntryV1(s.olt, onu, uni)
-		_ = json.NewEncoder(w).Encode(sadisConf)
+		// TODO format error
+		w.WriteHeader(http.StatusBadRequest)
+		_ = json.NewEncoder(w).Encode("Sadis v1 is not supported anymore, please go back to an earlier BBSim version")
 	} else if vars["version"] == "v2" {
+		w.WriteHeader(http.StatusOK)
 		sadisConf, _ := GetOnuEntryV2(s.olt, onu, uni)
 		_ = json.NewEncoder(w).Encode(sadisConf)
 	}
@@ -381,7 +359,7 @@
 
 // StartRestServer starts REST server which returns a SADIS configuration for the currently simulated OLT
 func StartRestServer(olt *devices.OltDevice, wg *sync.WaitGroup) {
-	addr := common.Options.BBSim.SadisRestAddress
+	addr := common.Config.BBSim.SadisRestAddress
 	sadisLogger.Infof("SADIS server listening on %s", addr)
 	s := &sadisServer{
 		olt: olt,
diff --git a/internal/bbsim/responders/sadis/sadis_test.go b/internal/bbsim/responders/sadis/sadis_test.go
index 04e0240..27e43bc 100644
--- a/internal/bbsim/responders/sadis/sadis_test.go
+++ b/internal/bbsim/responders/sadis/sadis_test.go
@@ -22,7 +22,6 @@
 	"testing"
 
 	"github.com/opencord/bbsim/internal/bbsim/devices"
-	"github.com/opencord/bbsim/internal/common"
 	"gotest.tools/assert"
 )
 
@@ -34,89 +33,78 @@
 	onu := &devices.Onu{
 		ID:        1,
 		PonPortID: 1,
-		STag:      900,
-		CTag:      923,
-		HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(1), byte(1)},
 		PortNo:    0,
 	}
+
+	mac := net.HardwareAddr{0x2e, 0x60, 0x01, byte(1), byte(1), byte(0)}
+
 	onu.SerialNumber = onu.NewSN(0, onu.PonPortID, onu.ID)
+	onu.Services = []devices.ServiceIf{
+		&devices.Service{Name: "hsia", CTag: 923, STag: 900, NeedsEapol: true, NeedsDhcp: true, NeedsIgmp: true, HwAddress: mac, TechnologyProfileID: 64},
+	}
 
 	return olt, onu
 }
 
-func TestSadisServer_GetOnuEntryV1(t *testing.T) {
+func TestSadisServer_GetOnuEntryV2(t *testing.T) {
 
 	olt, onu := createMockDevices()
 
 	uni := "1"
 
-	res, err := GetOnuEntryV1(olt, onu, uni)
-	if err != nil {
-		t.Fatal(err)
-	}
+	entry, err := GetOnuEntryV2(olt, onu, uni)
 
-	assert.Equal(t, res.ID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
-	assert.Equal(t, res.CTag, 923)
-	assert.Equal(t, res.STag, 900)
-	assert.Equal(t, res.RemoteID, string(olt.SerialNumber))
-	assert.Equal(t, res.DownstreamBandwidthProfile, "Default")
-	assert.Equal(t, res.UpstreamBandwidthProfile, "User_Bandwidth1")
-	assert.Equal(t, res.TechnologyProfileID, 64)
+	assert.NilError(t, err)
 
+	assert.Equal(t, entry.ID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
+	assert.Equal(t, entry.RemoteID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
+
+	assert.Equal(t, entry.UniTagList[0].PonCTag, 923)
+	assert.Equal(t, entry.UniTagList[0].PonSTag, 900)
+	assert.Equal(t, entry.UniTagList[0].DownstreamBandwidthProfile, "User_Bandwidth2")
+	assert.Equal(t, entry.UniTagList[0].UpstreamBandwidthProfile, "User_Bandwidth1")
+	assert.Equal(t, entry.UniTagList[0].TechnologyProfileID, 64)
+	assert.Equal(t, entry.UniTagList[0].IsDhcpRequired, true)
+	assert.Equal(t, entry.UniTagList[0].IsIgmpRequired, true)
 }
 
-func TestSadisServer_GetOnuEntryV2_Att(t *testing.T) {
+func TestSadisServer_GetOnuEntryV2_multi_service(t *testing.T) {
+
+	mac := net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(2)}
+
+	hsia := devices.Service{Name: "hsia", HwAddress: net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(1)},
+		CTag: 900, STag: 900, TechnologyProfileID: 64}
+
+	voip := devices.Service{Name: "voip", HwAddress: mac,
+		CTag: 901, STag: 900, TechnologyProfileID: 65}
+
+	vod := devices.Service{Name: "vod", HwAddress: net.HardwareAddr{0x2e, 0x60, byte(1), byte(1), byte(1), byte(3)},
+		CTag: 902, STag: 900, TechnologyProfileID: 66}
+
 	olt, onu := createMockDevices()
 
+	onu.Services = []devices.ServiceIf{&hsia, &voip, &vod}
+
 	uni := "1"
 
-	res, err := GetOnuEntryV2(olt, onu, uni)
-	if err != nil {
-		t.Fatal(err)
-	}
+	entry, err := GetOnuEntryV2(olt, onu, uni)
 
-	assert.Equal(t, res.ID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
-	assert.Equal(t, res.RemoteID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
+	assert.NilError(t, err)
 
-	// assert the correct type
-	uniTagList, ok := res.UniTagList[0].(SadisUniTagAtt)
-	if !ok {
-		t.Fatal("UniTagList has the wrong type")
-	}
+	assert.Equal(t, entry.ID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
+	assert.Equal(t, entry.RemoteID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
 
-	assert.Equal(t, uniTagList.PonCTag, 923)
-	assert.Equal(t, uniTagList.PonSTag, 900)
-	assert.Equal(t, uniTagList.DownstreamBandwidthProfile, "User_Bandwidth1")
-	assert.Equal(t, uniTagList.UpstreamBandwidthProfile, "Default")
-	assert.Equal(t, uniTagList.TechnologyProfileID, 64)
-	assert.Equal(t, uniTagList.IsDhcpRequired, false)
-	assert.Equal(t, uniTagList.IsIgmpRequired, false)
-}
+	assert.Equal(t, len(entry.UniTagList), 3)
 
-func TestSadisServer_GetOnuEntryV2_Dt(t *testing.T) {
-	common.Options.BBSim.SadisFormat = common.SadisFormatDt
-	olt, onu := createMockDevices()
+	assert.Equal(t, entry.UniTagList[0].PonCTag, 900)
+	assert.Equal(t, entry.UniTagList[0].PonSTag, 900)
+	assert.Equal(t, entry.UniTagList[0].TechnologyProfileID, 64)
 
-	uni := "1"
+	assert.Equal(t, entry.UniTagList[1].PonCTag, 901)
+	assert.Equal(t, entry.UniTagList[1].PonSTag, 900)
+	assert.Equal(t, entry.UniTagList[1].TechnologyProfileID, 65)
 
-	res, err := GetOnuEntryV2(olt, onu, uni)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	assert.Equal(t, res.ID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
-	assert.Equal(t, res.RemoteID, fmt.Sprintf("%s-%s", onu.Sn(), uni))
-
-	// assert the correct type
-	uniTagList, ok := res.UniTagList[0].(SadisUniTagDt)
-	if !ok {
-		t.Fatal("UniTagList has the wrong type")
-	}
-
-	assert.Equal(t, uniTagList.PonCTag, 4096)
-	assert.Equal(t, uniTagList.PonSTag, 900)
-	assert.Equal(t, uniTagList.DownstreamBandwidthProfile, "User_Bandwidth1")
-	assert.Equal(t, uniTagList.UpstreamBandwidthProfile, "Default")
-	assert.Equal(t, uniTagList.TechnologyProfileID, 64)
-	assert.Equal(t, uniTagList.UniTagMatch, 4096)
+	assert.Equal(t, entry.UniTagList[2].PonCTag, 902)
+	assert.Equal(t, entry.UniTagList[2].PonSTag, 900)
+	assert.Equal(t, entry.UniTagList[2].TechnologyProfileID, 66)
 }
diff --git a/internal/bbsimctl/commands/onu.go b/internal/bbsimctl/commands/onu.go
index 771df4c..20837b5 100644
--- a/internal/bbsimctl/commands/onu.go
+++ b/internal/bbsimctl/commands/onu.go
@@ -34,7 +34,8 @@
 )
 
 const (
-	DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .HwAddress }}\t{{ .STag }}\t{{ .CTag }}\t{{ .OperState }}\t{{ .InternalState }}"
+	DEFAULT_ONU_DEVICE_HEADER_FORMAT               = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}"
+	DEFAULT_ONU_DEVICE_HEADER_FORMAT_WITH_SERVICES = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}\t{{ .Services }}"
 )
 
 type OnuSnString string
@@ -44,9 +45,18 @@
 const IgmpLeaveKey string = "leave"
 const IgmpJoinKeyV3 string = "joinv3"
 
-type ONUList struct{}
+type ONUList struct {
+	Verbose bool `short:"v" long:"verbose" description:"Print all the informations we have about ONUs"`
+}
 
 type ONUGet struct {
+	Verbose bool `short:"v" long:"verbose" description:"Print all the informations we have about ONUs"`
+	Args    struct {
+		OnuSn OnuSnString
+	} `positional-args:"yes" required:"yes"`
+}
+
+type ONUServices struct {
 	Args struct {
 		OnuSn OnuSnString
 	} `positional-args:"yes" required:"yes"`
@@ -98,6 +108,7 @@
 type ONUOptions struct {
 	List              ONUList              `command:"list"`
 	Get               ONUGet               `command:"get"`
+	Services          ONUServices          `command:"services"`
 	ShutDown          ONUShutDown          `command:"shutdown"`
 	PowerOn           ONUPowerOn           `command:"poweron"`
 	RestartEapol      ONUEapolRestart      `command:"auth_restart"`
@@ -143,7 +154,12 @@
 	onus := getONUs()
 
 	// print out
-	tableFormat := format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT)
+	var tableFormat format.Format
+	if options.Verbose {
+		tableFormat = format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT_WITH_SERVICES)
+	} else {
+		tableFormat = format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT)
+	}
 	if err := tableFormat.Execute(os.Stdout, true, onus.Items); err != nil {
 		log.Fatalf("Error while formatting ONUs table: %s", err)
 	}
@@ -163,11 +179,16 @@
 	res, err := client.GetONU(ctx, &req)
 
 	if err != nil {
-		log.Fatalf("Cannot not shutdown ONU %s: %v", options.Args.OnuSn, err)
+		log.Fatalf("Cannot not get ONU %s: %v", options.Args.OnuSn, err)
 		return err
 	}
 
-	tableFormat := format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT)
+	var tableFormat format.Format
+	if options.Verbose {
+		tableFormat = format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT_WITH_SERVICES)
+	} else {
+		tableFormat = format.Format(DEFAULT_ONU_DEVICE_HEADER_FORMAT)
+	}
 	if err := tableFormat.Execute(os.Stdout, true, []*pb.ONU{res}); err != nil {
 		log.Fatalf("Error while formatting ONUs table: %s", err)
 	}
@@ -175,6 +196,31 @@
 	return nil
 }
 
+func (options *ONUServices) Execute(args []string) error {
+
+	client, conn := connect()
+	defer conn.Close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), config.GlobalConfig.Grpc.Timeout)
+	defer cancel()
+	req := pb.ONURequest{
+		SerialNumber: string(options.Args.OnuSn),
+	}
+	res, err := client.GetOnuServices(ctx, &req)
+
+	if err != nil {
+		log.Fatalf("Cannot not get services for ONU %s: %v", options.Args.OnuSn, err)
+		return err
+	}
+
+	tableFormat := format.Format(DEFAULT_SERVICE_HEADER_FORMAT)
+	if err := tableFormat.Execute(os.Stdout, true, res.Items); err != nil {
+		log.Fatalf("Error while formatting Services table: %s", err)
+	}
+
+	return nil
+}
+
 func (options *ONUShutDown) Execute(args []string) error {
 
 	client, conn := connect()
diff --git a/internal/bbsimctl/commands/services.go b/internal/bbsimctl/commands/services.go
new file mode 100644
index 0000000..929266d
--- /dev/null
+++ b/internal/bbsimctl/commands/services.go
@@ -0,0 +1,71 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package commands
+
+import (
+	"context"
+	"github.com/jessevdk/go-flags"
+	pb "github.com/opencord/bbsim/api/bbsim"
+	"github.com/opencord/bbsim/internal/bbsimctl/config"
+	"github.com/opencord/cordctl/pkg/format"
+	log "github.com/sirupsen/logrus"
+	"os"
+)
+
+const (
+	DEFAULT_SERVICE_HEADER_FORMAT = "table{{ .OnuSn }}\t{{ .Name }}\t{{ .HwAddress }}\t{{ .STag }}\t{{ .CTag }}\t{{ .NeedsEapol }}\t{{ .NeedsDhcp }}\t{{ .NeedsIgmp }}\t{{ .GemPort }}\t{{ .EapolState }}\t{{ .DhcpState }}"
+)
+
+type ServiceList struct{}
+
+type ServiceOptions struct {
+	List ServiceList `command:"list"`
+}
+
+func RegisterServiceCommands(parser *flags.Parser) {
+	_, _ = parser.AddCommand("service", "Service Commands", "Commands to interact with ONU Services", &ServiceOptions{})
+}
+
+func getServices() *pb.Services {
+
+	client, conn := connect()
+	defer conn.Close()
+
+	// Contact the server and print out its response.
+	ctx, cancel := context.WithTimeout(context.Background(), config.GlobalConfig.Grpc.Timeout)
+	defer cancel()
+
+	services, err := client.GetServices(ctx, &pb.Empty{})
+	if err != nil {
+		log.Fatalf("could not get OLT: %v", err)
+		return nil
+	}
+	return services
+}
+
+func (options *ServiceList) Execute(args []string) error {
+	services := getServices()
+
+	// print out
+	tableFormat := format.Format(DEFAULT_SERVICE_HEADER_FORMAT)
+	if err := tableFormat.Execute(os.Stdout, true, services.Items); err != nil {
+		log.Fatalf("Error while formatting ONUs table: %s", err)
+	}
+
+	return nil
+}
diff --git a/internal/common/kafka_topic_config_test.go b/internal/common/kafka_topic_config_test.go
index bd3dd2e..4264df0 100644
--- a/internal/common/kafka_topic_config_test.go
+++ b/internal/common/kafka_topic_config_test.go
@@ -64,12 +64,15 @@
 
 func TestInitializePublisher(t *testing.T) {
 	mockLib := mockSarama{}
+
+	Config = &GlobalConfig{}
+
 	err := InitializePublisher(mockLib.NewAsyncProducer, 0)
 
 	assert.Equal(t, err, nil)
 	assert.Equal(t, topic, "BBSim-OLT-0-Events")
 
-	Options.BBSim.KafkaEventTopic = "Testing-Topic"
+	Config.BBSim.KafkaEventTopic = "Testing-Topic"
 	err = InitializePublisher(mockLib.NewAsyncProducer, 0)
 	assert.Equal(t, topic, "Testing-Topic")
 	assert.Equal(t, err, nil)
diff --git a/internal/common/kafka_utils.go b/internal/common/kafka_utils.go
index c999539..c64646e 100644
--- a/internal/common/kafka_utils.go
+++ b/internal/common/kafka_utils.go
@@ -50,13 +50,13 @@
 	config.Metadata.Retry.Max = 10
 	config.Metadata.Retry.Backoff = 10 * time.Second
 	config.ClientID = "BBSim-OLT-" + strconv.Itoa(oltID)
-	if len(Options.BBSim.KafkaEventTopic) > 0 {
-		topic = Options.BBSim.KafkaEventTopic
+	if len(Config.BBSim.KafkaEventTopic) > 0 {
+		topic = Config.BBSim.KafkaEventTopic
 	} else {
 		topic = "BBSim-OLT-" + strconv.Itoa(oltID) + "-Events"
 	}
 
-	producer, err = NewAsyncProducer([]string{Options.BBSim.KafkaAddress}, config)
+	producer, err = NewAsyncProducer([]string{Config.BBSim.KafkaAddress}, config)
 	return err
 }
 
diff --git a/internal/common/option_test.go b/internal/common/option_test.go
new file mode 100644
index 0000000..4111ce3
--- /dev/null
+++ b/internal/common/option_test.go
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common
+
+import (
+	"gotest.tools/assert"
+	"testing"
+)
+
+func TestLoadBBSimServices(t *testing.T) {
+	services, err := loadBBSimServices("../../configs/att-services.yaml")
+
+	assert.NilError(t, err)
+
+	assert.Equal(t, services[0].Name, "hsia")
+	assert.Equal(t, services[0].CTag, 900)
+	assert.Equal(t, services[0].STag, 900)
+	assert.Equal(t, services[0].CTagAllocation, TagAllocationUnique.String())
+	assert.Equal(t, services[0].STagAllocation, TagAllocationShared.String())
+	assert.Equal(t, services[0].NeedsEapol, true)
+	assert.Equal(t, services[0].NeedsDchp, true)
+	assert.Equal(t, services[0].NeedsIgmp, false)
+	assert.Equal(t, services[0].TechnologyProfileID, 64)
+}
diff --git a/internal/common/options.go b/internal/common/options.go
index 5dd1553..567837b 100644
--- a/internal/common/options.go
+++ b/internal/common/options.go
@@ -23,8 +23,9 @@
 	"net"
 	"strings"
 
-	"github.com/ghodss/yaml"
+	"github.com/imdario/mergo"
 	log "github.com/sirupsen/logrus"
+	"gopkg.in/yaml.v2"
 )
 
 var tagAllocationValues = []string{
@@ -41,7 +42,7 @@
 
 func tagAllocationFromString(s string) (TagAllocation, error) {
 	for i, v := range tagAllocationValues {
-		if v == s {
+		if v == strings.TrimSpace(s) {
 			return TagAllocation(i), nil
 		}
 	}
@@ -57,47 +58,15 @@
 	TagAllocationUnique
 )
 
-var sadisFormatValues = []string{
-	"unknown",
-	"att",
-	"dt",
-	"tt",
-}
-
-type SadisFormat int
-
-func (s SadisFormat) String() string {
-	return sadisFormatValues[s]
-}
-
-func sadisFormatFromString(s string) (SadisFormat, error) {
-	for i, v := range sadisFormatValues {
-		if v == s {
-			return SadisFormat(i), nil
-		}
-	}
-	log.WithFields(log.Fields{
-		"ValidValues": strings.Join(sadisFormatValues[1:], ", "),
-	}).Errorf("%s-is-not-a-valid-sadis-format", s)
-	return SadisFormat(0), fmt.Errorf("%s-is-not-a-valid-sadis-format", s)
-}
-
-const (
-	_ SadisFormat = iota
-	SadisFormatAtt
-	SadisFormatDt
-	SadisFormatTt
-)
-
 type BBRCliOptions struct {
-	*BBSimYamlConfig
+	*GlobalConfig
 	BBSimIp      string
 	BBSimPort    string
 	BBSimApiPort string
 	LogFile      string
 }
 
-type BBSimYamlConfig struct {
+type GlobalConfig struct {
 	BBSim BBSimConfig
 	Olt   OltConfig
 	BBR   BBRConfig
@@ -120,32 +89,26 @@
 }
 
 type BBSimConfig struct {
-	DhcpRetry            bool          `yaml:"dhcp_retry"`
-	AuthRetry            bool          `yaml:"auth_retry"`
-	EnableIgmp           bool          `yaml:"enable_igmp"`
-	EnableDhcp           bool          `yaml:"enable_dhcp"`
-	EnableAuth           bool          `yaml:"enable_auth"`
-	LogLevel             string        `yaml:"log_level"`
-	LogCaller            bool          `yaml:"log_caller"`
-	Delay                int           `yaml:"delay"`
-	CpuProfile           *string       `yaml:"cpu_profile"`
-	CTagAllocation       TagAllocation `yaml:"c_tag_allocation"`
-	CTag                 int           `yaml:"c_tag"`
-	STagAllocation       TagAllocation `yaml:"s_tag_allocation"`
-	STag                 int           `yaml:"s_tag"`
-	OpenOltAddress       string        `yaml:"openolt_address"`
-	ApiAddress           string        `yaml:"api_address"`
-	RestApiAddress       string        `yaml:"rest_api_address"`
-	LegacyApiAddress     string        `yaml:"legacy_api_address"`
-	LegacyRestApiAddress string        `yaml:"legacy_rest_api_address"`
-	SadisRestAddress     string        `yaml:"sadis_rest_address"`
-	SadisServer          bool          `yaml:"sadis_server"`
-	SadisFormat          SadisFormat   `yaml:"sadis_format"`
-	KafkaAddress         string        `yaml:"kafka_address"`
-	Events               bool          `yaml:"enable_events"`
-	ControlledActivation string        `yaml:"controlled_activation"`
-	EnablePerf           bool          `yaml:"enable_perf"`
-	KafkaEventTopic      string        `yaml:"kafka_event_topic"`
+	ConfigFile           string
+	ServiceConfigFile    string
+	DhcpRetry            bool    `yaml:"dhcp_retry"`
+	AuthRetry            bool    `yaml:"auth_retry"`
+	LogLevel             string  `yaml:"log_level"`
+	LogCaller            bool    `yaml:"log_caller"`
+	Delay                int     `yaml:"delay"`
+	CpuProfile           *string `yaml:"cpu_profile"`
+	OpenOltAddress       string  `yaml:"openolt_address"`
+	ApiAddress           string  `yaml:"api_address"`
+	RestApiAddress       string  `yaml:"rest_api_address"`
+	LegacyApiAddress     string  `yaml:"legacy_api_address"`
+	LegacyRestApiAddress string  `yaml:"legacy_rest_api_address"`
+	SadisRestAddress     string  `yaml:"sadis_rest_address"`
+	SadisServer          bool    `yaml:"sadis_server"`
+	KafkaAddress         string  `yaml:"kafka_address"`
+	Events               bool    `yaml:"enable_events"`
+	ControlledActivation string  `yaml:"controlled_activation"`
+	EnablePerf           bool    `yaml:"enable_perf"`
+	KafkaEventTopic      string  `yaml:"kafka_event_topic"`
 }
 
 type BBRConfig struct {
@@ -154,24 +117,166 @@
 	LogCaller bool   `yaml:"log_caller"`
 }
 
-var Options *BBSimYamlConfig
-
-func init() {
-	// load settings from config file first
-	Options, _ = LoadBBSimConf("configs/bbsim.yaml")
+type ServiceYaml struct {
+	Name                string
+	CTag                int    `yaml:"c_tag"`
+	STag                int    `yaml:"s_tag"`
+	NeedsEapol          bool   `yaml:"needs_eapol"`
+	NeedsDchp           bool   `yaml:"needs_dhcp"`
+	NeedsIgmp           bool   `yaml:"needs_igmp"`
+	CTagAllocation      string `yaml:"c_tag_allocation"`
+	STagAllocation      string `yaml:"s_tag_allocation"`
+	TechnologyProfileID int    `yaml:"tp_id"`
+	UniTagMatch         int    `yaml:"uni_tag_match"`
+	ConfigureMacAddress bool   `yaml:"configure_mac_address"`
+	UsPonCTagPriority   int    `yaml:"us_pon_c_tag_priority"`
+	UsPonSTagPriority   int    `yaml:"us_pon_s_tag_priority"`
+	DsPonCTagPriority   int    `yaml:"ds_pon_c_tag_priority"`
+	DsPonSTagPriority   int    `yaml:"ds_pon_s_tag_priority"`
+}
+type YamlServiceConfig struct {
+	Workflow string
+	Services []ServiceYaml `yaml:"services,flow"`
 }
 
-func getDefaultOps() *BBSimYamlConfig {
+func (cfg *YamlServiceConfig) String() string {
+	str := fmt.Sprintf("[workflow: %s, Services: ", cfg.Workflow)
 
-	c := &BBSimYamlConfig{
+	for _, s := range cfg.Services {
+		str = fmt.Sprintf("%s[", str)
+		str = fmt.Sprintf("%sname=%s, c_tag=%d, s_tag=%d, ",
+			str, s.Name, s.CTag, s.STag)
+		str = fmt.Sprintf("%sc_tag_allocation=%s, s_tag_allocation=%s, ",
+			str, s.CTagAllocation, s.STagAllocation)
+		str = fmt.Sprintf("%sneeds_eapol=%t, needs_dhcp=%t, needs_igmp=%t",
+			str, s.NeedsEapol, s.NeedsDchp, s.NeedsIgmp)
+		str = fmt.Sprintf("%stp_id=%d, uni_tag_match=%d",
+			str, s.TechnologyProfileID, s.UniTagMatch)
+		str = fmt.Sprintf("%s]", str)
+	}
+	str = fmt.Sprintf("%s]", str)
+	return str
+}
+
+var (
+	Config   *GlobalConfig
+	Services []ServiceYaml
+)
+
+// Load the BBSim configuration. This is a combination of CLI parameters and YAML files
+// We proceed in this order:
+// - Read CLI parameters
+// - Using those we read the yaml files (config and services)
+// - we merge the configuration (CLI has priority over yaml files)
+func LoadConfig() {
+
+	Config = getDefaultOps()
+
+	cliConf := readCliParams()
+
+	yamlConf, err := loadBBSimConf(cliConf.BBSim.ConfigFile)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"file": cliConf.BBSim.ConfigFile,
+			"err":  err,
+		}).Fatal("Can't read config file")
+	}
+
+	// merging Yaml and Default Values
+	if err := mergo.Merge(Config, yamlConf, mergo.WithOverride); err != nil {
+		log.WithFields(log.Fields{
+			"err": err,
+		}).Fatal("Can't merge YAML and Config")
+	}
+
+	// merging CLI values on top of the yaml ones
+	if err := mergo.Merge(Config, cliConf, mergo.WithOverride); err != nil {
+		log.WithFields(log.Fields{
+			"err": err,
+		}).Fatal("Can't merge CLI and Config")
+	}
+
+	services, err := loadBBSimServices(Config.BBSim.ServiceConfigFile)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"file": Config.BBSim.ServiceConfigFile,
+			"err":  err,
+		}).Fatal("Can't read services file")
+	}
+
+	Services = services
+
+}
+
+func readCliParams() *GlobalConfig {
+
+	conf := GlobalConfig{}
+
+	configFile := flag.String("config", conf.BBSim.ConfigFile, "Configuration file path")
+	servicesFile := flag.String("services", conf.BBSim.ServiceConfigFile, "Service Configuration file path")
+
+	olt_id := flag.Int("olt_id", conf.Olt.ID, "OLT device ID")
+	nni := flag.Int("nni", int(conf.Olt.NniPorts), "Number of NNI ports per OLT device to be emulated")
+	pon := flag.Int("pon", int(conf.Olt.PonPorts), "Number of PON ports per OLT device to be emulated")
+	onu := flag.Int("onu", int(conf.Olt.OnusPonPort), "Number of ONU devices per PON port to be emulated")
+
+	openolt_address := flag.String("openolt_address", conf.BBSim.OpenOltAddress, "IP address:port")
+	api_address := flag.String("api_address", conf.BBSim.ApiAddress, "IP address:port")
+	rest_api_address := flag.String("rest_api_address", conf.BBSim.RestApiAddress, "IP address:port")
+
+	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
+
+	logLevel := flag.String("logLevel", conf.BBSim.LogLevel, "Set the log level (trace, debug, info, warn, error)")
+	logCaller := flag.Bool("logCaller", conf.BBSim.LogCaller, "Whether to print the caller filename or not")
+
+	delay := flag.Int("delay", conf.BBSim.Delay, "The delay between ONU DISCOVERY batches in milliseconds (1 ONU per each PON PORT at a time")
+
+	controlledActivation := flag.String("ca", conf.BBSim.ControlledActivation, "Set the mode for controlled activation of PON ports and ONUs")
+	enablePerf := flag.Bool("enableperf", conf.BBSim.EnablePerf, "Setting this flag will cause BBSim to not store data like traffic schedulers, flows of ONUs etc..")
+	enableEvents := flag.Bool("enableEvents", conf.BBSim.Events, "Enable sending BBSim events on configured kafka server")
+	kafkaAddress := flag.String("kafkaAddress", conf.BBSim.KafkaAddress, "IP:Port for kafka")
+	kafkaEventTopic := flag.String("kafkaEventTopic", conf.BBSim.KafkaEventTopic, "Ability to configure the topic on which BBSim publishes events on Kafka")
+	dhcpRetry := flag.Bool("dhcpRetry", conf.BBSim.DhcpRetry, "Set this flag if BBSim should retry DHCP upon failure until success")
+	authRetry := flag.Bool("authRetry", conf.BBSim.AuthRetry, "Set this flag if BBSim should retry EAPOL (Authentication) upon failure until success")
+	flag.Parse()
+
+	conf.Olt.ID = int(*olt_id)
+	conf.Olt.NniPorts = uint32(*nni)
+	conf.Olt.PonPorts = uint32(*pon)
+	conf.Olt.OnusPonPort = uint32(*onu)
+	conf.BBSim.ConfigFile = *configFile
+	conf.BBSim.ServiceConfigFile = *servicesFile
+	conf.BBSim.CpuProfile = profileCpu
+	conf.BBSim.LogLevel = *logLevel
+	conf.BBSim.LogCaller = *logCaller
+	conf.BBSim.Delay = *delay
+	conf.BBSim.ControlledActivation = *controlledActivation
+	conf.BBSim.EnablePerf = *enablePerf
+	conf.BBSim.Events = *enableEvents
+	conf.BBSim.KafkaAddress = *kafkaAddress
+	conf.BBSim.OpenOltAddress = *openolt_address
+	conf.BBSim.ApiAddress = *api_address
+	conf.BBSim.RestApiAddress = *rest_api_address
+	conf.BBSim.KafkaEventTopic = *kafkaEventTopic
+	conf.BBSim.AuthRetry = *authRetry
+	conf.BBSim.DhcpRetry = *dhcpRetry
+
+	// update device id if not set
+	if conf.Olt.DeviceId == "" {
+		conf.Olt.DeviceId = net.HardwareAddr{0xA, 0xA, 0xA, 0xA, 0xA, byte(conf.Olt.ID)}.String()
+	}
+
+	return &conf
+}
+
+func getDefaultOps() *GlobalConfig {
+
+	c := &GlobalConfig{
 		BBSimConfig{
-			STagAllocation:       TagAllocationShared,
-			STag:                 900,
-			CTagAllocation:       TagAllocationUnique,
-			CTag:                 900,
-			EnableIgmp:           false,
-			EnableDhcp:           false,
-			EnableAuth:           false,
+			ConfigFile:           "configs/bbsim.yaml",
+			ServiceConfigFile:    "configs/att-services.yaml",
 			LogLevel:             "debug",
 			LogCaller:            false,
 			Delay:                200,
@@ -182,7 +287,6 @@
 			LegacyRestApiAddress: ":50073",
 			SadisRestAddress:     ":50074",
 			SadisServer:          true,
-			SadisFormat:          SadisFormatAtt,
 			KafkaAddress:         ":9092",
 			Events:               false,
 			ControlledActivation: "default",
@@ -214,131 +318,74 @@
 }
 
 // LoadBBSimConf loads the BBSim configuration from a YAML file
-func LoadBBSimConf(filename string) (*BBSimYamlConfig, error) {
+func loadBBSimConf(filename string) (*GlobalConfig, error) {
 	yamlConfig := getDefaultOps()
 
 	yamlFile, err := ioutil.ReadFile(filename)
 	if err != nil {
-		fmt.Printf("Cannot load BBSim configuration file: %s. Using defaults.\n", err)
+		log.WithFields(log.Fields{
+			"err":      err,
+			"filename": filename,
+		}).Error("Cannot load BBSim configuration file. Using defaults.")
 		return yamlConfig, nil
 	}
 
 	err = yaml.Unmarshal(yamlFile, yamlConfig)
 	if err != nil {
-		fmt.Printf("Error parsing YAML file: %s\n", err)
+		return nil, err
 	}
 
-	// TODO convert from string to TagAllocation
-
 	return yamlConfig, nil
 }
 
-// GetBBSimOpts loads the BBSim configuration file and over-rides options with corresponding CLI flags if set
-func GetBBSimOpts() *BBSimYamlConfig {
-	conf := Options
+// LoadBBSimServices parses a file describing the services that need to be created for each UNI
+func loadBBSimServices(filename string) ([]ServiceYaml, error) {
 
-	olt_id := flag.Int("olt_id", conf.Olt.ID, "OLT device ID")
-	nni := flag.Int("nni", int(conf.Olt.NniPorts), "Number of NNI ports per OLT device to be emulated")
-	pon := flag.Int("pon", int(conf.Olt.PonPorts), "Number of PON ports per OLT device to be emulated")
-	onu := flag.Int("onu", int(conf.Olt.OnusPonPort), "Number of ONU devices per PON port to be emulated")
+	yamlServiceCfg := YamlServiceConfig{}
 
-	openolt_address := flag.String("openolt_address", conf.BBSim.OpenOltAddress, "IP address:port")
-	api_address := flag.String("api_address", conf.BBSim.ApiAddress, "IP address:port")
-	rest_api_address := flag.String("rest_api_address", conf.BBSim.RestApiAddress, "IP address:port")
-
-	s_tag_allocation := flag.String("s_tag_allocation", conf.BBSim.STagAllocation.String(), "Use 'unique' for incremental values, 'shared' to use the same value in all the ONUs")
-	s_tag := flag.Int("s_tag", conf.BBSim.STag, "S-Tag initial value")
-
-	c_tag_allocation := flag.String("c_tag_allocation", conf.BBSim.CTagAllocation.String(), "Use 'unique' for incremental values, 'shared' to use the same value in all the ONUs")
-	c_tag := flag.Int("c_tag", conf.BBSim.CTag, "C-Tag starting value, each ONU will get a sequential one (targeting 1024 ONUs per BBSim instance the range is big enough)")
-
-	sadisFormat := flag.String("sadisFormat", conf.BBSim.SadisFormat.String(), fmt.Sprintf("Which format should sadis expose? [%s]", strings.Join(sadisFormatValues[1:], "|")))
-
-	auth := flag.Bool("auth", conf.BBSim.EnableAuth, "Set this flag if you want authentication to start automatically")
-	dhcp := flag.Bool("dhcp", conf.BBSim.EnableDhcp, "Set this flag if you want DHCP to start automatically")
-	igmp := flag.Bool("igmp", conf.BBSim.EnableIgmp, "Set this flag if you want IGMP to start automatically")
-	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
-
-	logLevel := flag.String("logLevel", conf.BBSim.LogLevel, "Set the log level (trace, debug, info, warn, error)")
-	logCaller := flag.Bool("logCaller", conf.BBSim.LogCaller, "Whether to print the caller filename or not")
-
-	delay := flag.Int("delay", conf.BBSim.Delay, "The delay between ONU DISCOVERY batches in milliseconds (1 ONU per each PON PORT at a time")
-
-	controlledActivation := flag.String("ca", conf.BBSim.ControlledActivation, "Set the mode for controlled activation of PON ports and ONUs")
-	enablePerf := flag.Bool("enableperf", conf.BBSim.EnablePerf, "Setting this flag will cause BBSim to not store data like traffic schedulers, flows of ONUs etc..")
-	enableEvents := flag.Bool("enableEvents", conf.BBSim.Events, "Enable sending BBSim events on configured kafka server")
-	kafkaAddress := flag.String("kafkaAddress", conf.BBSim.KafkaAddress, "IP:Port for kafka")
-	kafkaEventTopic := flag.String("kafkaEventTopic", conf.BBSim.KafkaEventTopic, "Ability to configure the topic on which BBSim publishes events on Kafka")
-	dhcpRetry := flag.Bool("dhcpRetry", conf.BBSim.DhcpRetry, "Set this flag if BBSim should retry DHCP upon failure until success")
-	authRetry := flag.Bool("authRetry", conf.BBSim.AuthRetry, "Set this flag if BBSim should retry EAPOL (Authentication) upon failure until success")
-	flag.Parse()
-
-	sTagAlloc, err := tagAllocationFromString(*s_tag_allocation)
+	yamlFile, err := ioutil.ReadFile(filename)
 	if err != nil {
-		log.Fatal(err)
+		return nil, err
 	}
 
-	cTagAlloc, err := tagAllocationFromString(*c_tag_allocation)
+	err = yaml.Unmarshal([]byte(yamlFile), &yamlServiceCfg)
 	if err != nil {
-		log.Fatal(err)
+		return nil, err
 	}
 
-	sf, err := sadisFormatFromString(*sadisFormat)
-	if err != nil {
-		log.Fatal(err)
+	for _, service := range yamlServiceCfg.Services {
+
+		if service.CTagAllocation == "" || service.STagAllocation == "" {
+			log.Fatal("c_tag_allocation and s_tag_allocation are mandatory fields")
+		}
+
+		if _, err := tagAllocationFromString(string(service.CTagAllocation)); err != nil {
+			log.WithFields(log.Fields{
+				"err": err,
+			}).Fatal("c_tag_allocation is not valid")
+		}
 	}
 
-	if sf == SadisFormatTt {
-		log.Fatalf("Sadis format %s is not yet supported", sf.String())
-	}
-
-	conf.Olt.ID = int(*olt_id)
-	conf.Olt.NniPorts = uint32(*nni)
-	conf.Olt.PonPorts = uint32(*pon)
-	conf.Olt.OnusPonPort = uint32(*onu)
-	conf.BBSim.STagAllocation = sTagAlloc
-	conf.BBSim.STag = int(*s_tag)
-	conf.BBSim.CTagAllocation = cTagAlloc
-	conf.BBSim.CTag = int(*c_tag)
-	conf.BBSim.CpuProfile = profileCpu
-	conf.BBSim.LogLevel = *logLevel
-	conf.BBSim.LogCaller = *logCaller
-	conf.BBSim.EnableAuth = *auth
-	conf.BBSim.EnableDhcp = *dhcp
-	conf.BBSim.EnableIgmp = *igmp
-	conf.BBSim.Delay = *delay
-	conf.BBSim.ControlledActivation = *controlledActivation
-	conf.BBSim.EnablePerf = *enablePerf
-	conf.BBSim.Events = *enableEvents
-	conf.BBSim.KafkaAddress = *kafkaAddress
-	conf.BBSim.OpenOltAddress = *openolt_address
-	conf.BBSim.ApiAddress = *api_address
-	conf.BBSim.RestApiAddress = *rest_api_address
-	conf.BBSim.SadisFormat = sf
-	conf.BBSim.KafkaEventTopic = *kafkaEventTopic
-	conf.BBSim.AuthRetry = *authRetry
-	conf.BBSim.DhcpRetry = *dhcpRetry
-
-	// update device id if not set
-	if conf.Olt.DeviceId == "" {
-		conf.Olt.DeviceId = net.HardwareAddr{0xA, 0xA, 0xA, 0xA, 0xA, byte(conf.Olt.ID)}.String()
-	}
-
-	Options = conf
-	return conf
+	log.WithFields(log.Fields{
+		"services": yamlServiceCfg.String(),
+	}).Debug("BBSim services description correctly loaded")
+	return yamlServiceCfg.Services, nil
 }
 
+// This is only used by BBR
 func GetBBROpts() BBRCliOptions {
 
+	LoadConfig()
+
 	bbsimIp := flag.String("bbsimIp", "127.0.0.1", "BBSim IP")
 	bbsimPort := flag.String("bbsimPort", "50060", "BBSim Port")
 	bbsimApiPort := flag.String("bbsimApiPort", "50070", "BBSim API Port")
 	logFile := flag.String("logfile", "", "Log to a file")
 
-	options := GetBBSimOpts()
+	flag.Parse()
 
 	bbrOptions := BBRCliOptions{
-		options,
+		Config,
 		*bbsimIp,
 		*bbsimPort,
 		*bbsimApiPort,
diff --git a/vendor/github.com/ghodss/yaml/.gitignore b/vendor/github.com/ghodss/yaml/.gitignore
deleted file mode 100644
index e256a31..0000000
--- a/vendor/github.com/ghodss/yaml/.gitignore
+++ /dev/null
@@ -1,20 +0,0 @@
-# OSX leaves these everywhere on SMB shares
-._*
-
-# Eclipse files
-.classpath
-.project
-.settings/**
-
-# Emacs save files
-*~
-
-# Vim-related files
-[._]*.s[a-w][a-z]
-[._]s[a-w][a-z]
-*.un~
-Session.vim
-.netrwhist
-
-# Go test binaries
-*.test
diff --git a/vendor/github.com/ghodss/yaml/.travis.yml b/vendor/github.com/ghodss/yaml/.travis.yml
deleted file mode 100644
index 0e9d6ed..0000000
--- a/vendor/github.com/ghodss/yaml/.travis.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-language: go
-go:
-  - 1.3
-  - 1.4
-script:
-  - go test
-  - go build
diff --git a/vendor/github.com/ghodss/yaml/LICENSE b/vendor/github.com/ghodss/yaml/LICENSE
deleted file mode 100644
index 7805d36..0000000
--- a/vendor/github.com/ghodss/yaml/LICENSE
+++ /dev/null
@@ -1,50 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Sam Ghods
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-
-Copyright (c) 2012 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-   * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/ghodss/yaml/README.md b/vendor/github.com/ghodss/yaml/README.md
deleted file mode 100644
index 0200f75..0000000
--- a/vendor/github.com/ghodss/yaml/README.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# YAML marshaling and unmarshaling support for Go
-
-[![Build Status](https://travis-ci.org/ghodss/yaml.svg)](https://travis-ci.org/ghodss/yaml)
-
-## Introduction
-
-A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
-
-In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
-
-## Compatibility
-
-This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
-
-## Caveats
-
-**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example:
-
-```
-BAD:
-	exampleKey: !!binary gIGC
-
-GOOD:
-	exampleKey: gIGC
-... and decode the base64 data in your code.
-```
-
-**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys.
-
-## Installation and usage
-
-To install, run:
-
-```
-$ go get github.com/ghodss/yaml
-```
-
-And import using:
-
-```
-import "github.com/ghodss/yaml"
-```
-
-Usage is very similar to the JSON library:
-
-```go
-package main
-
-import (
-	"fmt"
-
-	"github.com/ghodss/yaml"
-)
-
-type Person struct {
-	Name string `json:"name"` // Affects YAML field names too.
-	Age  int    `json:"age"`
-}
-
-func main() {
-	// Marshal a Person struct to YAML.
-	p := Person{"John", 30}
-	y, err := yaml.Marshal(p)
-	if err != nil {
-		fmt.Printf("err: %v\n", err)
-		return
-	}
-	fmt.Println(string(y))
-	/* Output:
-	age: 30
-	name: John
-	*/
-
-	// Unmarshal the YAML back into a Person struct.
-	var p2 Person
-	err = yaml.Unmarshal(y, &p2)
-	if err != nil {
-		fmt.Printf("err: %v\n", err)
-		return
-	}
-	fmt.Println(p2)
-	/* Output:
-	{John 30}
-	*/
-}
-```
-
-`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
-
-```go
-package main
-
-import (
-	"fmt"
-
-	"github.com/ghodss/yaml"
-)
-
-func main() {
-	j := []byte(`{"name": "John", "age": 30}`)
-	y, err := yaml.JSONToYAML(j)
-	if err != nil {
-		fmt.Printf("err: %v\n", err)
-		return
-	}
-	fmt.Println(string(y))
-	/* Output:
-	name: John
-	age: 30
-	*/
-	j2, err := yaml.YAMLToJSON(y)
-	if err != nil {
-		fmt.Printf("err: %v\n", err)
-		return
-	}
-	fmt.Println(string(j2))
-	/* Output:
-	{"age":30,"name":"John"}
-	*/
-}
-```
diff --git a/vendor/github.com/ghodss/yaml/fields.go b/vendor/github.com/ghodss/yaml/fields.go
deleted file mode 100644
index 5860074..0000000
--- a/vendor/github.com/ghodss/yaml/fields.go
+++ /dev/null
@@ -1,501 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-package yaml
-
-import (
-	"bytes"
-	"encoding"
-	"encoding/json"
-	"reflect"
-	"sort"
-	"strings"
-	"sync"
-	"unicode"
-	"unicode/utf8"
-)
-
-// indirect walks down v allocating pointers as needed,
-// until it gets to a non-pointer.
-// if it encounters an Unmarshaler, indirect stops and returns that.
-// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
-func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
-	// If v is a named type and is addressable,
-	// start with its address, so that if the type has pointer methods,
-	// we find them.
-	if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
-		v = v.Addr()
-	}
-	for {
-		// Load value from interface, but only if the result will be
-		// usefully addressable.
-		if v.Kind() == reflect.Interface && !v.IsNil() {
-			e := v.Elem()
-			if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
-				v = e
-				continue
-			}
-		}
-
-		if v.Kind() != reflect.Ptr {
-			break
-		}
-
-		if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
-			break
-		}
-		if v.IsNil() {
-			if v.CanSet() {
-				v.Set(reflect.New(v.Type().Elem()))
-			} else {
-				v = reflect.New(v.Type().Elem())
-			}
-		}
-		if v.Type().NumMethod() > 0 {
-			if u, ok := v.Interface().(json.Unmarshaler); ok {
-				return u, nil, reflect.Value{}
-			}
-			if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
-				return nil, u, reflect.Value{}
-			}
-		}
-		v = v.Elem()
-	}
-	return nil, nil, v
-}
-
-// A field represents a single field found in a struct.
-type field struct {
-	name      string
-	nameBytes []byte                 // []byte(name)
-	equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
-
-	tag       bool
-	index     []int
-	typ       reflect.Type
-	omitEmpty bool
-	quoted    bool
-}
-
-func fillField(f field) field {
-	f.nameBytes = []byte(f.name)
-	f.equalFold = foldFunc(f.nameBytes)
-	return f
-}
-
-// byName sorts field by name, breaking ties with depth,
-// then breaking ties with "name came from json tag", then
-// breaking ties with index sequence.
-type byName []field
-
-func (x byName) Len() int { return len(x) }
-
-func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-
-func (x byName) Less(i, j int) bool {
-	if x[i].name != x[j].name {
-		return x[i].name < x[j].name
-	}
-	if len(x[i].index) != len(x[j].index) {
-		return len(x[i].index) < len(x[j].index)
-	}
-	if x[i].tag != x[j].tag {
-		return x[i].tag
-	}
-	return byIndex(x).Less(i, j)
-}
-
-// byIndex sorts field by index sequence.
-type byIndex []field
-
-func (x byIndex) Len() int { return len(x) }
-
-func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-
-func (x byIndex) Less(i, j int) bool {
-	for k, xik := range x[i].index {
-		if k >= len(x[j].index) {
-			return false
-		}
-		if xik != x[j].index[k] {
-			return xik < x[j].index[k]
-		}
-	}
-	return len(x[i].index) < len(x[j].index)
-}
-
-// typeFields returns a list of fields that JSON should recognize for the given type.
-// The algorithm is breadth-first search over the set of structs to include - the top struct
-// and then any reachable anonymous structs.
-func typeFields(t reflect.Type) []field {
-	// Anonymous fields to explore at the current level and the next.
-	current := []field{}
-	next := []field{{typ: t}}
-
-	// Count of queued names for current level and the next.
-	count := map[reflect.Type]int{}
-	nextCount := map[reflect.Type]int{}
-
-	// Types already visited at an earlier level.
-	visited := map[reflect.Type]bool{}
-
-	// Fields found.
-	var fields []field
-
-	for len(next) > 0 {
-		current, next = next, current[:0]
-		count, nextCount = nextCount, map[reflect.Type]int{}
-
-		for _, f := range current {
-			if visited[f.typ] {
-				continue
-			}
-			visited[f.typ] = true
-
-			// Scan f.typ for fields to include.
-			for i := 0; i < f.typ.NumField(); i++ {
-				sf := f.typ.Field(i)
-				if sf.PkgPath != "" { // unexported
-					continue
-				}
-				tag := sf.Tag.Get("json")
-				if tag == "-" {
-					continue
-				}
-				name, opts := parseTag(tag)
-				if !isValidTag(name) {
-					name = ""
-				}
-				index := make([]int, len(f.index)+1)
-				copy(index, f.index)
-				index[len(f.index)] = i
-
-				ft := sf.Type
-				if ft.Name() == "" && ft.Kind() == reflect.Ptr {
-					// Follow pointer.
-					ft = ft.Elem()
-				}
-
-				// Record found field and index sequence.
-				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
-					tagged := name != ""
-					if name == "" {
-						name = sf.Name
-					}
-					fields = append(fields, fillField(field{
-						name:      name,
-						tag:       tagged,
-						index:     index,
-						typ:       ft,
-						omitEmpty: opts.Contains("omitempty"),
-						quoted:    opts.Contains("string"),
-					}))
-					if count[f.typ] > 1 {
-						// If there were multiple instances, add a second,
-						// so that the annihilation code will see a duplicate.
-						// It only cares about the distinction between 1 or 2,
-						// so don't bother generating any more copies.
-						fields = append(fields, fields[len(fields)-1])
-					}
-					continue
-				}
-
-				// Record new anonymous struct to explore in next round.
-				nextCount[ft]++
-				if nextCount[ft] == 1 {
-					next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
-				}
-			}
-		}
-	}
-
-	sort.Sort(byName(fields))
-
-	// Delete all fields that are hidden by the Go rules for embedded fields,
-	// except that fields with JSON tags are promoted.
-
-	// The fields are sorted in primary order of name, secondary order
-	// of field index length. Loop over names; for each name, delete
-	// hidden fields by choosing the one dominant field that survives.
-	out := fields[:0]
-	for advance, i := 0, 0; i < len(fields); i += advance {
-		// One iteration per name.
-		// Find the sequence of fields with the name of this first field.
-		fi := fields[i]
-		name := fi.name
-		for advance = 1; i+advance < len(fields); advance++ {
-			fj := fields[i+advance]
-			if fj.name != name {
-				break
-			}
-		}
-		if advance == 1 { // Only one field with this name
-			out = append(out, fi)
-			continue
-		}
-		dominant, ok := dominantField(fields[i : i+advance])
-		if ok {
-			out = append(out, dominant)
-		}
-	}
-
-	fields = out
-	sort.Sort(byIndex(fields))
-
-	return fields
-}
-
-// dominantField looks through the fields, all of which are known to
-// have the same name, to find the single field that dominates the
-// others using Go's embedding rules, modified by the presence of
-// JSON tags. If there are multiple top-level fields, the boolean
-// will be false: This condition is an error in Go and we skip all
-// the fields.
-func dominantField(fields []field) (field, bool) {
-	// The fields are sorted in increasing index-length order. The winner
-	// must therefore be one with the shortest index length. Drop all
-	// longer entries, which is easy: just truncate the slice.
-	length := len(fields[0].index)
-	tagged := -1 // Index of first tagged field.
-	for i, f := range fields {
-		if len(f.index) > length {
-			fields = fields[:i]
-			break
-		}
-		if f.tag {
-			if tagged >= 0 {
-				// Multiple tagged fields at the same level: conflict.
-				// Return no field.
-				return field{}, false
-			}
-			tagged = i
-		}
-	}
-	if tagged >= 0 {
-		return fields[tagged], true
-	}
-	// All remaining fields have the same length. If there's more than one,
-	// we have a conflict (two fields named "X" at the same level) and we
-	// return no field.
-	if len(fields) > 1 {
-		return field{}, false
-	}
-	return fields[0], true
-}
-
-var fieldCache struct {
-	sync.RWMutex
-	m map[reflect.Type][]field
-}
-
-// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
-func cachedTypeFields(t reflect.Type) []field {
-	fieldCache.RLock()
-	f := fieldCache.m[t]
-	fieldCache.RUnlock()
-	if f != nil {
-		return f
-	}
-
-	// Compute fields without lock.
-	// Might duplicate effort but won't hold other computations back.
-	f = typeFields(t)
-	if f == nil {
-		f = []field{}
-	}
-
-	fieldCache.Lock()
-	if fieldCache.m == nil {
-		fieldCache.m = map[reflect.Type][]field{}
-	}
-	fieldCache.m[t] = f
-	fieldCache.Unlock()
-	return f
-}
-
-func isValidTag(s string) bool {
-	if s == "" {
-		return false
-	}
-	for _, c := range s {
-		switch {
-		case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
-			// Backslash and quote chars are reserved, but
-			// otherwise any punctuation chars are allowed
-			// in a tag name.
-		default:
-			if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-const (
-	caseMask     = ^byte(0x20) // Mask to ignore case in ASCII.
-	kelvin       = '\u212a'
-	smallLongEss = '\u017f'
-)
-
-// foldFunc returns one of four different case folding equivalence
-// functions, from most general (and slow) to fastest:
-//
-// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
-// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
-// 3) asciiEqualFold, no special, but includes non-letters (including _)
-// 4) simpleLetterEqualFold, no specials, no non-letters.
-//
-// The letters S and K are special because they map to 3 runes, not just 2:
-//  * S maps to s and to U+017F 'Å¿' Latin small letter long s
-//  * k maps to K and to U+212A 'K' Kelvin sign
-// See http://play.golang.org/p/tTxjOc0OGo
-//
-// The returned function is specialized for matching against s and
-// should only be given s. It's not curried for performance reasons.
-func foldFunc(s []byte) func(s, t []byte) bool {
-	nonLetter := false
-	special := false // special letter
-	for _, b := range s {
-		if b >= utf8.RuneSelf {
-			return bytes.EqualFold
-		}
-		upper := b & caseMask
-		if upper < 'A' || upper > 'Z' {
-			nonLetter = true
-		} else if upper == 'K' || upper == 'S' {
-			// See above for why these letters are special.
-			special = true
-		}
-	}
-	if special {
-		return equalFoldRight
-	}
-	if nonLetter {
-		return asciiEqualFold
-	}
-	return simpleLetterEqualFold
-}
-
-// equalFoldRight is a specialization of bytes.EqualFold when s is
-// known to be all ASCII (including punctuation), but contains an 's',
-// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
-// See comments on foldFunc.
-func equalFoldRight(s, t []byte) bool {
-	for _, sb := range s {
-		if len(t) == 0 {
-			return false
-		}
-		tb := t[0]
-		if tb < utf8.RuneSelf {
-			if sb != tb {
-				sbUpper := sb & caseMask
-				if 'A' <= sbUpper && sbUpper <= 'Z' {
-					if sbUpper != tb&caseMask {
-						return false
-					}
-				} else {
-					return false
-				}
-			}
-			t = t[1:]
-			continue
-		}
-		// sb is ASCII and t is not. t must be either kelvin
-		// sign or long s; sb must be s, S, k, or K.
-		tr, size := utf8.DecodeRune(t)
-		switch sb {
-		case 's', 'S':
-			if tr != smallLongEss {
-				return false
-			}
-		case 'k', 'K':
-			if tr != kelvin {
-				return false
-			}
-		default:
-			return false
-		}
-		t = t[size:]
-
-	}
-	if len(t) > 0 {
-		return false
-	}
-	return true
-}
-
-// asciiEqualFold is a specialization of bytes.EqualFold for use when
-// s is all ASCII (but may contain non-letters) and contains no
-// special-folding letters.
-// See comments on foldFunc.
-func asciiEqualFold(s, t []byte) bool {
-	if len(s) != len(t) {
-		return false
-	}
-	for i, sb := range s {
-		tb := t[i]
-		if sb == tb {
-			continue
-		}
-		if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
-			if sb&caseMask != tb&caseMask {
-				return false
-			}
-		} else {
-			return false
-		}
-	}
-	return true
-}
-
-// simpleLetterEqualFold is a specialization of bytes.EqualFold for
-// use when s is all ASCII letters (no underscores, etc) and also
-// doesn't contain 'k', 'K', 's', or 'S'.
-// See comments on foldFunc.
-func simpleLetterEqualFold(s, t []byte) bool {
-	if len(s) != len(t) {
-		return false
-	}
-	for i, b := range s {
-		if b&caseMask != t[i]&caseMask {
-			return false
-		}
-	}
-	return true
-}
-
-// tagOptions is the string following a comma in a struct field's "json"
-// tag, or the empty string. It does not include the leading comma.
-type tagOptions string
-
-// parseTag splits a struct field's json tag into its name and
-// comma-separated options.
-func parseTag(tag string) (string, tagOptions) {
-	if idx := strings.Index(tag, ","); idx != -1 {
-		return tag[:idx], tagOptions(tag[idx+1:])
-	}
-	return tag, tagOptions("")
-}
-
-// Contains reports whether a comma-separated list of options
-// contains a particular substr flag. substr must be surrounded by a
-// string boundary or commas.
-func (o tagOptions) Contains(optionName string) bool {
-	if len(o) == 0 {
-		return false
-	}
-	s := string(o)
-	for s != "" {
-		var next string
-		i := strings.Index(s, ",")
-		if i >= 0 {
-			s, next = s[:i], s[i+1:]
-		}
-		if s == optionName {
-			return true
-		}
-		s = next
-	}
-	return false
-}
diff --git a/vendor/github.com/ghodss/yaml/yaml.go b/vendor/github.com/ghodss/yaml/yaml.go
deleted file mode 100644
index 4fb4054..0000000
--- a/vendor/github.com/ghodss/yaml/yaml.go
+++ /dev/null
@@ -1,277 +0,0 @@
-package yaml
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"reflect"
-	"strconv"
-
-	"gopkg.in/yaml.v2"
-)
-
-// Marshals the object into JSON then converts JSON to YAML and returns the
-// YAML.
-func Marshal(o interface{}) ([]byte, error) {
-	j, err := json.Marshal(o)
-	if err != nil {
-		return nil, fmt.Errorf("error marshaling into JSON: %v", err)
-	}
-
-	y, err := JSONToYAML(j)
-	if err != nil {
-		return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
-	}
-
-	return y, nil
-}
-
-// Converts YAML to JSON then uses JSON to unmarshal into an object.
-func Unmarshal(y []byte, o interface{}) error {
-	vo := reflect.ValueOf(o)
-	j, err := yamlToJSON(y, &vo)
-	if err != nil {
-		return fmt.Errorf("error converting YAML to JSON: %v", err)
-	}
-
-	err = json.Unmarshal(j, o)
-	if err != nil {
-		return fmt.Errorf("error unmarshaling JSON: %v", err)
-	}
-
-	return nil
-}
-
-// Convert JSON to YAML.
-func JSONToYAML(j []byte) ([]byte, error) {
-	// Convert the JSON to an object.
-	var jsonObj interface{}
-	// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
-	// Go JSON library doesn't try to pick the right number type (int, float,
-	// etc.) when unmarshalling to interface{}, it just picks float64
-	// universally. go-yaml does go through the effort of picking the right
-	// number type, so we can preserve number type throughout this process.
-	err := yaml.Unmarshal(j, &jsonObj)
-	if err != nil {
-		return nil, err
-	}
-
-	// Marshal this object into YAML.
-	return yaml.Marshal(jsonObj)
-}
-
-// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
-// this method should be a no-op.
-//
-// Things YAML can do that are not supported by JSON:
-// * In YAML you can have binary and null keys in your maps. These are invalid
-//   in JSON. (int and float keys are converted to strings.)
-// * Binary data in YAML with the !!binary tag is not supported. If you want to
-//   use binary data with this library, encode the data as base64 as usual but do
-//   not use the !!binary tag in your YAML. This will ensure the original base64
-//   encoded data makes it all the way through to the JSON.
-func YAMLToJSON(y []byte) ([]byte, error) {
-	return yamlToJSON(y, nil)
-}
-
-func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
-	// Convert the YAML to an object.
-	var yamlObj interface{}
-	err := yaml.Unmarshal(y, &yamlObj)
-	if err != nil {
-		return nil, err
-	}
-
-	// YAML objects are not completely compatible with JSON objects (e.g. you
-	// can have non-string keys in YAML). So, convert the YAML-compatible object
-	// to a JSON-compatible object, failing with an error if irrecoverable
-	// incompatibilties happen along the way.
-	jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
-	if err != nil {
-		return nil, err
-	}
-
-	// Convert this object to JSON and return the data.
-	return json.Marshal(jsonObj)
-}
-
-func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
-	var err error
-
-	// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
-	// interface). We pass decodingNull as false because we're not actually
-	// decoding into the value, we're just checking if the ultimate target is a
-	// string.
-	if jsonTarget != nil {
-		ju, tu, pv := indirect(*jsonTarget, false)
-		// We have a JSON or Text Umarshaler at this level, so we can't be trying
-		// to decode into a string.
-		if ju != nil || tu != nil {
-			jsonTarget = nil
-		} else {
-			jsonTarget = &pv
-		}
-	}
-
-	// If yamlObj is a number or a boolean, check if jsonTarget is a string -
-	// if so, coerce.  Else return normal.
-	// If yamlObj is a map or array, find the field that each key is
-	// unmarshaling to, and when you recurse pass the reflect.Value for that
-	// field back into this function.
-	switch typedYAMLObj := yamlObj.(type) {
-	case map[interface{}]interface{}:
-		// JSON does not support arbitrary keys in a map, so we must convert
-		// these keys to strings.
-		//
-		// From my reading of go-yaml v2 (specifically the resolve function),
-		// keys can only have the types string, int, int64, float64, binary
-		// (unsupported), or null (unsupported).
-		strMap := make(map[string]interface{})
-		for k, v := range typedYAMLObj {
-			// Resolve the key to a string first.
-			var keyString string
-			switch typedKey := k.(type) {
-			case string:
-				keyString = typedKey
-			case int:
-				keyString = strconv.Itoa(typedKey)
-			case int64:
-				// go-yaml will only return an int64 as a key if the system
-				// architecture is 32-bit and the key's value is between 32-bit
-				// and 64-bit. Otherwise the key type will simply be int.
-				keyString = strconv.FormatInt(typedKey, 10)
-			case float64:
-				// Stolen from go-yaml to use the same conversion to string as
-				// the go-yaml library uses to convert float to string when
-				// Marshaling.
-				s := strconv.FormatFloat(typedKey, 'g', -1, 32)
-				switch s {
-				case "+Inf":
-					s = ".inf"
-				case "-Inf":
-					s = "-.inf"
-				case "NaN":
-					s = ".nan"
-				}
-				keyString = s
-			case bool:
-				if typedKey {
-					keyString = "true"
-				} else {
-					keyString = "false"
-				}
-			default:
-				return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
-					reflect.TypeOf(k), k, v)
-			}
-
-			// jsonTarget should be a struct or a map. If it's a struct, find
-			// the field it's going to map to and pass its reflect.Value. If
-			// it's a map, find the element type of the map and pass the
-			// reflect.Value created from that type. If it's neither, just pass
-			// nil - JSON conversion will error for us if it's a real issue.
-			if jsonTarget != nil {
-				t := *jsonTarget
-				if t.Kind() == reflect.Struct {
-					keyBytes := []byte(keyString)
-					// Find the field that the JSON library would use.
-					var f *field
-					fields := cachedTypeFields(t.Type())
-					for i := range fields {
-						ff := &fields[i]
-						if bytes.Equal(ff.nameBytes, keyBytes) {
-							f = ff
-							break
-						}
-						// Do case-insensitive comparison.
-						if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
-							f = ff
-						}
-					}
-					if f != nil {
-						// Find the reflect.Value of the most preferential
-						// struct field.
-						jtf := t.Field(f.index[0])
-						strMap[keyString], err = convertToJSONableObject(v, &jtf)
-						if err != nil {
-							return nil, err
-						}
-						continue
-					}
-				} else if t.Kind() == reflect.Map {
-					// Create a zero value of the map's element type to use as
-					// the JSON target.
-					jtv := reflect.Zero(t.Type().Elem())
-					strMap[keyString], err = convertToJSONableObject(v, &jtv)
-					if err != nil {
-						return nil, err
-					}
-					continue
-				}
-			}
-			strMap[keyString], err = convertToJSONableObject(v, nil)
-			if err != nil {
-				return nil, err
-			}
-		}
-		return strMap, nil
-	case []interface{}:
-		// We need to recurse into arrays in case there are any
-		// map[interface{}]interface{}'s inside and to convert any
-		// numbers to strings.
-
-		// If jsonTarget is a slice (which it really should be), find the
-		// thing it's going to map to. If it's not a slice, just pass nil
-		// - JSON conversion will error for us if it's a real issue.
-		var jsonSliceElemValue *reflect.Value
-		if jsonTarget != nil {
-			t := *jsonTarget
-			if t.Kind() == reflect.Slice {
-				// By default slices point to nil, but we need a reflect.Value
-				// pointing to a value of the slice type, so we create one here.
-				ev := reflect.Indirect(reflect.New(t.Type().Elem()))
-				jsonSliceElemValue = &ev
-			}
-		}
-
-		// Make and use a new array.
-		arr := make([]interface{}, len(typedYAMLObj))
-		for i, v := range typedYAMLObj {
-			arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
-			if err != nil {
-				return nil, err
-			}
-		}
-		return arr, nil
-	default:
-		// If the target type is a string and the YAML type is a number,
-		// convert the YAML type to a string.
-		if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
-			// Based on my reading of go-yaml, it may return int, int64,
-			// float64, or uint64.
-			var s string
-			switch typedVal := typedYAMLObj.(type) {
-			case int:
-				s = strconv.FormatInt(int64(typedVal), 10)
-			case int64:
-				s = strconv.FormatInt(typedVal, 10)
-			case float64:
-				s = strconv.FormatFloat(typedVal, 'g', -1, 32)
-			case uint64:
-				s = strconv.FormatUint(typedVal, 10)
-			case bool:
-				if typedVal {
-					s = "true"
-				} else {
-					s = "false"
-				}
-			}
-			if len(s) > 0 {
-				yamlObj = interface{}(s)
-			}
-		}
-		return yamlObj, nil
-	}
-
-	return nil, nil
-}
diff --git a/vendor/github.com/imdario/mergo/.deepsource.toml b/vendor/github.com/imdario/mergo/.deepsource.toml
new file mode 100644
index 0000000..8a0681a
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/.deepsource.toml
@@ -0,0 +1,12 @@
+version = 1
+
+test_patterns = [
+  "*_test.go"
+]
+
+[[analyzers]]
+name = "go"
+enabled = true
+
+  [analyzers.meta]
+  import_path = "github.com/imdario/mergo"
\ No newline at end of file
diff --git a/vendor/github.com/imdario/mergo/.gitignore b/vendor/github.com/imdario/mergo/.gitignore
new file mode 100644
index 0000000..529c341
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/.gitignore
@@ -0,0 +1,33 @@
+#### joe made this: http://goel.io/joe
+
+#### go ####
+# Binaries for programs and plugins
+*.exe
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
+.glide/
+
+#### vim ####
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-v][a-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+
+# Temporary
+.netrwhist
+*~
+# Auto-generated tag files
+tags
diff --git a/vendor/github.com/imdario/mergo/.travis.yml b/vendor/github.com/imdario/mergo/.travis.yml
new file mode 100644
index 0000000..dad2972
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+install:
+  - go get -t
+  - go get golang.org/x/tools/cmd/cover
+  - go get github.com/mattn/goveralls
+script:
+  - go test -race -v ./...
+after_script:
+  - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
diff --git a/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md b/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..469b449
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/github.com/imdario/mergo/LICENSE b/vendor/github.com/imdario/mergo/LICENSE
new file mode 100644
index 0000000..6866802
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2013 Dario Castañé. All rights reserved.
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/imdario/mergo/README.md b/vendor/github.com/imdario/mergo/README.md
new file mode 100644
index 0000000..876abb5
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/README.md
@@ -0,0 +1,247 @@
+# Mergo
+
+
+[![GoDoc][3]][4]
+[![GitHub release][5]][6]
+[![GoCard][7]][8]
+[![Build Status][1]][2]
+[![Coverage Status][9]][10]
+[![Sourcegraph][11]][12]
+[![FOSSA Status][13]][14]
+
+[![GoCenter Kudos][15]][16]
+
+[1]: https://travis-ci.org/imdario/mergo.png
+[2]: https://travis-ci.org/imdario/mergo
+[3]: https://godoc.org/github.com/imdario/mergo?status.svg
+[4]: https://godoc.org/github.com/imdario/mergo
+[5]: https://img.shields.io/github/release/imdario/mergo.svg
+[6]: https://github.com/imdario/mergo/releases
+[7]: https://goreportcard.com/badge/imdario/mergo
+[8]: https://goreportcard.com/report/github.com/imdario/mergo
+[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
+[10]: https://coveralls.io/github/imdario/mergo?branch=master
+[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
+[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
+[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
+[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
+[15]: https://search.gocenter.io/api/ui/badge/github.com%2Fimdario%2Fmergo
+[16]: https://search.gocenter.io/github.com/imdario/mergo
+
+A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
+
+Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
+
+Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
+
+## Status
+
+It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
+
+### Important note
+
+Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds suppot for go modules.
+
+Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
+
+If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
+
+### Donations
+
+If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
+
+<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
+[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo)
+[![Beerpay](https://beerpay.io/imdario/mergo/make-wish.svg)](https://beerpay.io/imdario/mergo)
+<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
+
+### Mergo in the wild
+
+- [moby/moby](https://github.com/moby/moby)
+- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
+- [vmware/dispatch](https://github.com/vmware/dispatch)
+- [Shopify/themekit](https://github.com/Shopify/themekit)
+- [imdario/zas](https://github.com/imdario/zas)
+- [matcornic/hermes](https://github.com/matcornic/hermes)
+- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
+- [kataras/iris](https://github.com/kataras/iris)
+- [michaelsauter/crane](https://github.com/michaelsauter/crane)
+- [go-task/task](https://github.com/go-task/task)
+- [sensu/uchiwa](https://github.com/sensu/uchiwa)
+- [ory/hydra](https://github.com/ory/hydra)
+- [sisatech/vcli](https://github.com/sisatech/vcli)
+- [dairycart/dairycart](https://github.com/dairycart/dairycart)
+- [projectcalico/felix](https://github.com/projectcalico/felix)
+- [resin-os/balena](https://github.com/resin-os/balena)
+- [go-kivik/kivik](https://github.com/go-kivik/kivik)
+- [Telefonica/govice](https://github.com/Telefonica/govice)
+- [supergiant/supergiant](supergiant/supergiant)
+- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
+- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
+- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
+- [EagerIO/Stout](https://github.com/EagerIO/Stout)
+- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
+- [russross/canvasassignments](https://github.com/russross/canvasassignments)
+- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
+- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
+- [divshot/gitling](https://github.com/divshot/gitling)
+- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
+- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
+- [elwinar/rambler](https://github.com/elwinar/rambler)
+- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
+- [jfbus/impressionist](https://github.com/jfbus/impressionist)
+- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
+- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
+- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
+- [thoas/picfit](https://github.com/thoas/picfit)
+- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
+- [jnuthong/item_search](https://github.com/jnuthong/item_search)
+- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
+- [janoszen/containerssh](https://github.com/janoszen/containerssh)
+
+## Install
+
+    go get github.com/imdario/mergo
+
+    // use in your .go code
+    import (
+        "github.com/imdario/mergo"
+    )
+
+## Usage
+
+You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
+
+```go
+if err := mergo.Merge(&dst, src); err != nil {
+    // ...
+}
+```
+
+Also, you can merge overwriting values using the transformer `WithOverride`.
+
+```go
+if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
+    // ...
+}
+```
+
+Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
+
+```go
+if err := mergo.Map(&dst, srcMap); err != nil {
+    // ...
+}
+```
+
+Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
+
+Here is a nice example:
+
+```go
+package main
+
+import (
+	"fmt"
+	"github.com/imdario/mergo"
+)
+
+type Foo struct {
+	A string
+	B int64
+}
+
+func main() {
+	src := Foo{
+		A: "one",
+		B: 2,
+	}
+	dest := Foo{
+		A: "two",
+	}
+	mergo.Merge(&dest, src)
+	fmt.Println(dest)
+	// Will print
+	// {two 2}
+}
+```
+
+Note: if test are failing due missing package, please execute:
+
+    go get gopkg.in/yaml.v2
+
+### Transformers
+
+Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
+
+```go
+package main
+
+import (
+	"fmt"
+	"github.com/imdario/mergo"
+        "reflect"
+        "time"
+)
+
+type timeTransformer struct {
+}
+
+func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
+	if typ == reflect.TypeOf(time.Time{}) {
+		return func(dst, src reflect.Value) error {
+			if dst.CanSet() {
+				isZero := dst.MethodByName("IsZero")
+				result := isZero.Call([]reflect.Value{})
+				if result[0].Bool() {
+					dst.Set(src)
+				}
+			}
+			return nil
+		}
+	}
+	return nil
+}
+
+type Snapshot struct {
+	Time time.Time
+	// ...
+}
+
+func main() {
+	src := Snapshot{time.Now()}
+	dest := Snapshot{}
+	mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
+	fmt.Println(dest)
+	// Will print
+	// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
+}
+```
+
+
+## Contact me
+
+If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
+
+## About
+
+Written by [Dario Castañé](http://dario.im).
+
+## Top Contributors
+
+[![0](https://sourcerer.io/fame/imdario/imdario/mergo/images/0)](https://sourcerer.io/fame/imdario/imdario/mergo/links/0)
+[![1](https://sourcerer.io/fame/imdario/imdario/mergo/images/1)](https://sourcerer.io/fame/imdario/imdario/mergo/links/1)
+[![2](https://sourcerer.io/fame/imdario/imdario/mergo/images/2)](https://sourcerer.io/fame/imdario/imdario/mergo/links/2)
+[![3](https://sourcerer.io/fame/imdario/imdario/mergo/images/3)](https://sourcerer.io/fame/imdario/imdario/mergo/links/3)
+[![4](https://sourcerer.io/fame/imdario/imdario/mergo/images/4)](https://sourcerer.io/fame/imdario/imdario/mergo/links/4)
+[![5](https://sourcerer.io/fame/imdario/imdario/mergo/images/5)](https://sourcerer.io/fame/imdario/imdario/mergo/links/5)
+[![6](https://sourcerer.io/fame/imdario/imdario/mergo/images/6)](https://sourcerer.io/fame/imdario/imdario/mergo/links/6)
+[![7](https://sourcerer.io/fame/imdario/imdario/mergo/images/7)](https://sourcerer.io/fame/imdario/imdario/mergo/links/7)
+
+
+## License
+
+[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
+
+
+[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
diff --git a/vendor/github.com/imdario/mergo/doc.go b/vendor/github.com/imdario/mergo/doc.go
new file mode 100644
index 0000000..fcd985f
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/doc.go
@@ -0,0 +1,143 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
+
+Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
+
+Status
+
+It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
+
+Important note
+
+Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
+
+Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
+
+If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u github.com/imdario/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
+
+Install
+
+Do your usual installation procedure:
+
+    go get github.com/imdario/mergo
+
+    // use in your .go code
+    import (
+        "github.com/imdario/mergo"
+    )
+
+Usage
+
+You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
+
+	if err := mergo.Merge(&dst, src); err != nil {
+		// ...
+	}
+
+Also, you can merge overwriting values using the transformer WithOverride.
+
+	if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
+		// ...
+	}
+
+Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
+
+	if err := mergo.Map(&dst, srcMap); err != nil {
+		// ...
+	}
+
+Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
+
+Here is a nice example:
+
+	package main
+
+	import (
+		"fmt"
+		"github.com/imdario/mergo"
+	)
+
+	type Foo struct {
+		A string
+		B int64
+	}
+
+	func main() {
+		src := Foo{
+			A: "one",
+			B: 2,
+		}
+		dest := Foo{
+			A: "two",
+		}
+		mergo.Merge(&dest, src)
+		fmt.Println(dest)
+		// Will print
+		// {two 2}
+	}
+
+Transformers
+
+Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
+
+	package main
+
+	import (
+		"fmt"
+		"github.com/imdario/mergo"
+			"reflect"
+			"time"
+	)
+
+	type timeTransformer struct {
+	}
+
+	func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
+		if typ == reflect.TypeOf(time.Time{}) {
+			return func(dst, src reflect.Value) error {
+				if dst.CanSet() {
+					isZero := dst.MethodByName("IsZero")
+					result := isZero.Call([]reflect.Value{})
+					if result[0].Bool() {
+						dst.Set(src)
+					}
+				}
+				return nil
+			}
+		}
+		return nil
+	}
+
+	type Snapshot struct {
+		Time time.Time
+		// ...
+	}
+
+	func main() {
+		src := Snapshot{time.Now()}
+		dest := Snapshot{}
+		mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
+		fmt.Println(dest)
+		// Will print
+		// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
+	}
+
+Contact me
+
+If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
+
+About
+
+Written by Dario Castañé: https://da.rio.hn
+
+License
+
+BSD 3-Clause license, as Go language.
+
+*/
+package mergo
diff --git a/vendor/github.com/imdario/mergo/go.mod b/vendor/github.com/imdario/mergo/go.mod
new file mode 100644
index 0000000..3d689d9
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/go.mod
@@ -0,0 +1,5 @@
+module github.com/imdario/mergo
+
+go 1.13
+
+require gopkg.in/yaml.v2 v2.3.0
diff --git a/vendor/github.com/imdario/mergo/go.sum b/vendor/github.com/imdario/mergo/go.sum
new file mode 100644
index 0000000..168980d
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/go.sum
@@ -0,0 +1,4 @@
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go
new file mode 100644
index 0000000..a13a7ee
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/map.go
@@ -0,0 +1,178 @@
+// Copyright 2014 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"fmt"
+	"reflect"
+	"unicode"
+	"unicode/utf8"
+)
+
+func changeInitialCase(s string, mapper func(rune) rune) string {
+	if s == "" {
+		return s
+	}
+	r, n := utf8.DecodeRuneInString(s)
+	return string(mapper(r)) + s[n:]
+}
+
+func isExported(field reflect.StructField) bool {
+	r, _ := utf8.DecodeRuneInString(field.Name)
+	return r >= 'A' && r <= 'Z'
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
+	overwrite := config.Overwrite
+	if dst.CanAddr() {
+		addr := dst.UnsafeAddr()
+		h := 17 * addr
+		seen := visited[h]
+		typ := dst.Type()
+		for p := seen; p != nil; p = p.next {
+			if p.ptr == addr && p.typ == typ {
+				return nil
+			}
+		}
+		// Remember, remember...
+		visited[h] = &visit{addr, typ, seen}
+	}
+	zeroValue := reflect.Value{}
+	switch dst.Kind() {
+	case reflect.Map:
+		dstMap := dst.Interface().(map[string]interface{})
+		for i, n := 0, src.NumField(); i < n; i++ {
+			srcType := src.Type()
+			field := srcType.Field(i)
+			if !isExported(field) {
+				continue
+			}
+			fieldName := field.Name
+			fieldName = changeInitialCase(fieldName, unicode.ToLower)
+			if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
+				dstMap[fieldName] = src.Field(i).Interface()
+			}
+		}
+	case reflect.Ptr:
+		if dst.IsNil() {
+			v := reflect.New(dst.Type().Elem())
+			dst.Set(v)
+		}
+		dst = dst.Elem()
+		fallthrough
+	case reflect.Struct:
+		srcMap := src.Interface().(map[string]interface{})
+		for key := range srcMap {
+			config.overwriteWithEmptyValue = true
+			srcValue := srcMap[key]
+			fieldName := changeInitialCase(key, unicode.ToUpper)
+			dstElement := dst.FieldByName(fieldName)
+			if dstElement == zeroValue {
+				// We discard it because the field doesn't exist.
+				continue
+			}
+			srcElement := reflect.ValueOf(srcValue)
+			dstKind := dstElement.Kind()
+			srcKind := srcElement.Kind()
+			if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
+				srcElement = srcElement.Elem()
+				srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
+			} else if dstKind == reflect.Ptr {
+				// Can this work? I guess it can't.
+				if srcKind != reflect.Ptr && srcElement.CanAddr() {
+					srcPtr := srcElement.Addr()
+					srcElement = reflect.ValueOf(srcPtr)
+					srcKind = reflect.Ptr
+				}
+			}
+
+			if !srcElement.IsValid() {
+				continue
+			}
+			if srcKind == dstKind {
+				if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+					return
+				}
+			} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
+				if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+					return
+				}
+			} else if srcKind == reflect.Map {
+				if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
+					return
+				}
+			} else {
+				return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
+			}
+		}
+	}
+	return
+}
+
+// Map sets fields' values in dst from src.
+// src can be a map with string keys or a struct. dst must be the opposite:
+// if src is a map, dst must be a valid pointer to struct. If src is a struct,
+// dst must be map[string]interface{}.
+// It won't merge unexported (private) fields and will do recursively
+// any exported field.
+// If dst is a map, keys will be src fields' names in lower camel case.
+// Missing key in src that doesn't match a field in dst will be skipped. This
+// doesn't apply if dst is a map.
+// This is separated method from Merge because it is cleaner and it keeps sane
+// semantics: merging equal types, mapping different (restricted) types.
+func Map(dst, src interface{}, opts ...func(*Config)) error {
+	return _map(dst, src, opts...)
+}
+
+// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
+// non-empty src attribute values.
+// Deprecated: Use Map(…) with WithOverride
+func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
+	return _map(dst, src, append(opts, WithOverride)...)
+}
+
+func _map(dst, src interface{}, opts ...func(*Config)) error {
+	if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
+		return ErrNonPointerAgument
+	}
+	var (
+		vDst, vSrc reflect.Value
+		err        error
+	)
+	config := &Config{}
+
+	for _, opt := range opts {
+		opt(config)
+	}
+
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+		return err
+	}
+	// To be friction-less, we redirect equal-type arguments
+	// to deepMerge. Only because arguments can be anything.
+	if vSrc.Kind() == vDst.Kind() {
+		return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+	}
+	switch vSrc.Kind() {
+	case reflect.Struct:
+		if vDst.Kind() != reflect.Map {
+			return ErrExpectedMapAsDestination
+		}
+	case reflect.Map:
+		if vDst.Kind() != reflect.Struct {
+			return ErrExpectedStructAsDestination
+		}
+	default:
+		return ErrNotSupported
+	}
+	return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+}
diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go
new file mode 100644
index 0000000..afa84a1
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/merge.go
@@ -0,0 +1,375 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func hasMergeableFields(dst reflect.Value) (exported bool) {
+	for i, n := 0, dst.NumField(); i < n; i++ {
+		field := dst.Type().Field(i)
+		if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
+			exported = exported || hasMergeableFields(dst.Field(i))
+		} else if isExportedComponent(&field) {
+			exported = exported || len(field.PkgPath) == 0
+		}
+	}
+	return
+}
+
+func isExportedComponent(field *reflect.StructField) bool {
+	pkgPath := field.PkgPath
+	if len(pkgPath) > 0 {
+		return false
+	}
+	c := field.Name[0]
+	if 'a' <= c && c <= 'z' || c == '_' {
+		return false
+	}
+	return true
+}
+
+type Config struct {
+	Overwrite                    bool
+	AppendSlice                  bool
+	TypeCheck                    bool
+	Transformers                 Transformers
+	overwriteWithEmptyValue      bool
+	overwriteSliceWithEmptyValue bool
+	sliceDeepCopy                bool
+	debug                        bool
+}
+
+type Transformers interface {
+	Transformer(reflect.Type) func(dst, src reflect.Value) error
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
+	overwrite := config.Overwrite
+	typeCheck := config.TypeCheck
+	overwriteWithEmptySrc := config.overwriteWithEmptyValue
+	overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
+	sliceDeepCopy := config.sliceDeepCopy
+
+	if !src.IsValid() {
+		return
+	}
+	if dst.CanAddr() {
+		addr := dst.UnsafeAddr()
+		h := 17 * addr
+		seen := visited[h]
+		typ := dst.Type()
+		for p := seen; p != nil; p = p.next {
+			if p.ptr == addr && p.typ == typ {
+				return nil
+			}
+		}
+		// Remember, remember...
+		visited[h] = &visit{addr, typ, seen}
+	}
+
+	if config.Transformers != nil && !isEmptyValue(dst) {
+		if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
+			err = fn(dst, src)
+			return
+		}
+	}
+
+	switch dst.Kind() {
+	case reflect.Struct:
+		if hasMergeableFields(dst) {
+			for i, n := 0, dst.NumField(); i < n; i++ {
+				if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
+					return
+				}
+			}
+		} else {
+			if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
+				dst.Set(src)
+			}
+		}
+	case reflect.Map:
+		if dst.IsNil() && !src.IsNil() {
+			dst.Set(reflect.MakeMap(dst.Type()))
+		}
+
+		if src.Kind() != reflect.Map {
+			if overwrite {
+				dst.Set(src)
+			}
+			return
+		}
+
+		for _, key := range src.MapKeys() {
+			srcElement := src.MapIndex(key)
+			if !srcElement.IsValid() {
+				continue
+			}
+			dstElement := dst.MapIndex(key)
+			switch srcElement.Kind() {
+			case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
+				if srcElement.IsNil() {
+					if overwrite {
+						dst.SetMapIndex(key, srcElement)
+					}
+					continue
+				}
+				fallthrough
+			default:
+				if !srcElement.CanInterface() {
+					continue
+				}
+				switch reflect.TypeOf(srcElement.Interface()).Kind() {
+				case reflect.Struct:
+					fallthrough
+				case reflect.Ptr:
+					fallthrough
+				case reflect.Map:
+					srcMapElm := srcElement
+					dstMapElm := dstElement
+					if srcMapElm.CanInterface() {
+						srcMapElm = reflect.ValueOf(srcMapElm.Interface())
+						if dstMapElm.IsValid() {
+							dstMapElm = reflect.ValueOf(dstMapElm.Interface())
+						}
+					}
+					if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
+						return
+					}
+				case reflect.Slice:
+					srcSlice := reflect.ValueOf(srcElement.Interface())
+
+					var dstSlice reflect.Value
+					if !dstElement.IsValid() || dstElement.IsNil() {
+						dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
+					} else {
+						dstSlice = reflect.ValueOf(dstElement.Interface())
+					}
+
+					if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
+						if typeCheck && srcSlice.Type() != dstSlice.Type() {
+							return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
+						}
+						dstSlice = srcSlice
+					} else if config.AppendSlice {
+						if srcSlice.Type() != dstSlice.Type() {
+							return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
+						}
+						dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
+					} else if sliceDeepCopy {
+						i := 0
+						for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
+							srcElement := srcSlice.Index(i)
+							dstElement := dstSlice.Index(i)
+
+							if srcElement.CanInterface() {
+								srcElement = reflect.ValueOf(srcElement.Interface())
+							}
+							if dstElement.CanInterface() {
+								dstElement = reflect.ValueOf(dstElement.Interface())
+							}
+
+							if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+								return
+							}
+						}
+
+					}
+					dst.SetMapIndex(key, dstSlice)
+				}
+			}
+			if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
+				continue
+			}
+
+			if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) {
+				if dst.IsNil() {
+					dst.Set(reflect.MakeMap(dst.Type()))
+				}
+				dst.SetMapIndex(key, srcElement)
+			}
+		}
+	case reflect.Slice:
+		if !dst.CanSet() {
+			break
+		}
+		if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
+			dst.Set(src)
+		} else if config.AppendSlice {
+			if src.Type() != dst.Type() {
+				return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
+			}
+			dst.Set(reflect.AppendSlice(dst, src))
+		} else if sliceDeepCopy {
+			for i := 0; i < src.Len() && i < dst.Len(); i++ {
+				srcElement := src.Index(i)
+				dstElement := dst.Index(i)
+				if srcElement.CanInterface() {
+					srcElement = reflect.ValueOf(srcElement.Interface())
+				}
+				if dstElement.CanInterface() {
+					dstElement = reflect.ValueOf(dstElement.Interface())
+				}
+
+				if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+					return
+				}
+			}
+		}
+	case reflect.Ptr:
+		fallthrough
+	case reflect.Interface:
+		if isReflectNil(src) {
+			if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
+				dst.Set(src)
+			}
+			break
+		}
+
+		if src.Kind() != reflect.Interface {
+			if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
+				if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
+					dst.Set(src)
+				}
+			} else if src.Kind() == reflect.Ptr {
+				if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+					return
+				}
+			} else if dst.Elem().Type() == src.Type() {
+				if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
+					return
+				}
+			} else {
+				return ErrDifferentArgumentsTypes
+			}
+			break
+		}
+
+		if dst.IsNil() || overwrite {
+			if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
+				dst.Set(src)
+			}
+			break
+		}
+
+		if dst.Elem().Kind() == src.Elem().Kind() {
+			if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+				return
+			}
+			break
+		}
+	default:
+		mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc)
+		if mustSet {
+			if dst.CanSet() {
+				dst.Set(src)
+			} else {
+				dst = src
+			}
+		}
+	}
+
+	return
+}
+
+// Merge will fill any empty for value type attributes on the dst struct using corresponding
+// src attributes if they themselves are not empty. dst and src must be valid same-type structs
+// and dst must be a pointer to struct.
+// It won't merge unexported (private) fields and will do recursively any exported field.
+func Merge(dst, src interface{}, opts ...func(*Config)) error {
+	return merge(dst, src, opts...)
+}
+
+// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
+// non-empty src attribute values.
+// Deprecated: use Merge(…) with WithOverride
+func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
+	return merge(dst, src, append(opts, WithOverride)...)
+}
+
+// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
+func WithTransformers(transformers Transformers) func(*Config) {
+	return func(config *Config) {
+		config.Transformers = transformers
+	}
+}
+
+// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
+func WithOverride(config *Config) {
+	config.Overwrite = true
+}
+
+// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
+func WithOverwriteWithEmptyValue(config *Config) {
+	config.Overwrite = true
+	config.overwriteWithEmptyValue = true
+}
+
+// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
+func WithOverrideEmptySlice(config *Config) {
+	config.overwriteSliceWithEmptyValue = true
+}
+
+// WithAppendSlice will make merge append slices instead of overwriting it.
+func WithAppendSlice(config *Config) {
+	config.AppendSlice = true
+}
+
+// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
+func WithTypeCheck(config *Config) {
+	config.TypeCheck = true
+}
+
+// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
+func WithSliceDeepCopy(config *Config) {
+	config.sliceDeepCopy = true
+	config.Overwrite = true
+}
+
+func merge(dst, src interface{}, opts ...func(*Config)) error {
+	if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
+		return ErrNonPointerAgument
+	}
+	var (
+		vDst, vSrc reflect.Value
+		err        error
+	)
+
+	config := &Config{}
+
+	for _, opt := range opts {
+		opt(config)
+	}
+
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+		return err
+	}
+	if vDst.Type() != vSrc.Type() {
+		return ErrDifferentArgumentsTypes
+	}
+	return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+}
+
+// IsReflectNil is the reflect value provided nil
+func isReflectNil(v reflect.Value) bool {
+	k := v.Kind()
+	switch k {
+	case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
+		// Both interface and slice are nil if first word is 0.
+		// Both are always bigger than a word; assume flagIndir.
+		return v.IsNil()
+	default:
+		return false
+	}
+}
diff --git a/vendor/github.com/imdario/mergo/mergo.go b/vendor/github.com/imdario/mergo/mergo.go
new file mode 100644
index 0000000..3cc926c
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/mergo.go
@@ -0,0 +1,78 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"errors"
+	"reflect"
+)
+
+// Errors reported by Mergo when it finds invalid arguments.
+var (
+	ErrNilArguments                = errors.New("src and dst must not be nil")
+	ErrDifferentArgumentsTypes     = errors.New("src and dst must be of same type")
+	ErrNotSupported                = errors.New("only structs and maps are supported")
+	ErrExpectedMapAsDestination    = errors.New("dst was expected to be a map")
+	ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
+	ErrNonPointerAgument           = errors.New("dst must be a pointer")
+)
+
+// During deepMerge, must keep track of checks that are
+// in progress.  The comparison algorithm assumes that all
+// checks in progress are true when it reencounters them.
+// Visited are stored in a map indexed by 17 * a1 + a2;
+type visit struct {
+	ptr  uintptr
+	typ  reflect.Type
+	next *visit
+}
+
+// From src/pkg/encoding/json/encode.go.
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		if v.IsNil() {
+			return true
+		}
+		return isEmptyValue(v.Elem())
+	case reflect.Func:
+		return v.IsNil()
+	case reflect.Invalid:
+		return true
+	}
+	return false
+}
+
+func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
+	if dst == nil || src == nil {
+		err = ErrNilArguments
+		return
+	}
+	vDst = reflect.ValueOf(dst).Elem()
+	if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
+		err = ErrNotSupported
+		return
+	}
+	vSrc = reflect.ValueOf(src)
+	// We check if vSrc is a pointer to dereference it.
+	if vSrc.Kind() == reflect.Ptr {
+		vSrc = vSrc.Elem()
+	}
+	return
+}
diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go
index 1f7e87e..d2c2308 100644
--- a/vendor/gopkg.in/yaml.v2/apic.go
+++ b/vendor/gopkg.in/yaml.v2/apic.go
@@ -86,6 +86,7 @@
 		raw_buffer: make([]byte, 0, output_raw_buffer_size),
 		states:     make([]yaml_emitter_state_t, 0, initial_stack_size),
 		events:     make([]yaml_event_t, 0, initial_queue_size),
+		best_width: -1,
 	}
 }
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6e2ad93..caebce8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -16,8 +16,6 @@
 github.com/eapache/go-xerial-snappy
 # github.com/eapache/queue v1.1.0
 github.com/eapache/queue
-# github.com/ghodss/yaml v1.0.0
-github.com/ghodss/yaml
 # github.com/golang/protobuf v1.3.2
 github.com/golang/protobuf/descriptor
 github.com/golang/protobuf/jsonpb
@@ -51,6 +49,8 @@
 github.com/grpc-ecosystem/grpc-gateway/utilities
 # github.com/hashicorp/go-uuid v1.0.2
 github.com/hashicorp/go-uuid
+# github.com/imdario/mergo v0.3.11
+github.com/imdario/mergo
 # github.com/jcmturner/gofork v1.0.0
 github.com/jcmturner/gofork/encoding/asn1
 github.com/jcmturner/gofork/x/crypto/pbkdf2
@@ -195,7 +195,7 @@
 # gopkg.in/jcmturner/rpc.v1 v1.1.0
 gopkg.in/jcmturner/rpc.v1/mstypes
 gopkg.in/jcmturner/rpc.v1/ndr
-# gopkg.in/yaml.v2 v2.2.8
+# gopkg.in/yaml.v2 v2.3.0
 gopkg.in/yaml.v2
 # gotest.tools v2.2.0+incompatible
 gotest.tools/assert